How recreate a hash digest of a multihash in IPFS - ipfs

Assuming I'm adding data to IPFS like this:
$ echo Hello World | ipfs add
This will give me QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u - a CID which is a Base58 encoded Multihash.
Converting it to Base16, tells me that the hash digest for what IPFS has added is a SHA2-256 hash:
12 - 20 - 74410577111096cd817a3faed78630f2245636beded412d3b212a2e09ba593ca
<hash-type> - <hash-length> - <hash-digest>
I know that IPFS doesn't just hash the data, but actually serializes it as Unixfs protobuf first and then puts that in a dag.
I'd like to demystify, how to get to the 74410577111096cd817a3faed78630f2245636beded412d3b212a2e09ba593ca but I'm not really sure how to get hold of the created dag that holds the Unixfs protobuf with the data.
For example I can write the serialized raw data to disk and inspect it with a protobuf decoder:
$ ipfs block get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u > /tmp/block.raw
$ protoc --decode_raw < /tmp/block.raw
This will give me the serialized data in a readable format:
1 {
1: 2
2: "Hello World\n"
3: 12
}
However, piping that through SHA-256 still gives me a different hash, which makes sense because IPFS puts the protobuf in a dag and multihashes that one.
$ protoc --decode_raw < /tmp/block.raw | shasum -a 256
So I decided to figure out how to get hold of that dag node, hash it myself to get to the hash I'm looking for.
I was hoping using ipfs dag get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u will give me a multihash that can then be decoded, but it turns out it returns some other data hash that I don't know how to inspect:
$ ipfs dag get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
$ {"data":"CAISDEhlbGxvIFdvcmxkChgM","links":[]}
Any ideas on how to decode data from here?
UPDATE
data is a Base64 representation of the original data: https://github.com/ipfs/go-ipfs/issues/4115

The hash you're looking for is the hash of the output of ipfs block get QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u. IPFS hashes the encoded value.
Instead of running:
protoc --decode_raw < /tmp/block.raw | shasum -a 256
Just run:
shasum -a 256 < /tmp/block.raw
but it turns out it returns some other data hash that I don't know how to inspect
That's because we currently use a protobuf inside of a protobuf. The outer protobuf has the structure {Data: DATA, Links: [{Name: ..., Size: ..., Hash: ...}]}.
In:
1 {
1: 2
2: "Hello World\n"
3: 12
}
The 1 { ... } part is the Data field of the outer protobuf. However, protoc --decode_raw *recursively* decodes this object so it decodes theData` field to:
Field 1 (DataType): 2 (File)
Field 2 (Data): "Hello World\n"
Field 3 (Filesize): 12 (bytes)
For context, the relevant protobuf definitions are:
Outer:
// An IPFS MerkleDAG Link
message PBLink {
// multihash of the target object
optional bytes Hash = 1;
// utf string name. should be unique per object
optional string Name = 2;
// cumulative size of target object
optional uint64 Tsize = 3;
}
// An IPFS MerkleDAG Node
message PBNode {
// refs to other objects
repeated PBLink Links = 2;
// opaque user data
optional bytes Data = 1;
}
Inner:
message Data {
enum DataType {
Raw = 0;
Directory = 1;
File = 2;
Metadata = 3;
Symlink = 4;
HAMTShard = 5;
}
required DataType Type = 1;
optional bytes Data = 2;
optional uint64 filesize = 3;
repeated uint64 blocksizes = 4;
optional uint64 hashType = 5;
optional uint64 fanout = 6;
}
message Metadata {
optional string MimeType = 1;
}

I'm not sure what that encoding is but you can unmarshal the dag data field like this in js-ipfs:
const IPFS = require('ipfs')
const Unixfs = require('ipfs-unixfs')
const ipfs = new IPFS
ipfs.dag.get('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u', (err, d) => {
console.log(Unixfs.unmarshal(d.value.data).data.toString()))
// prints Hello World
})

According to Steven's answer, using protobuf is working. Here is the full code of my approach.
ipfs.proto
syntax = "proto3";
message PBNode {
bytes Data = 1;
}
message PBLink {
bytes Hash = 1;
string Name = 2;
uint64 Tsize = 3;
}
message Data {
enum DataType {
Raw = 0;
Directory = 1;
File = 2;
Metadata = 3;
Symlink = 4;
HAMTShard = 5;
}
DataType Type = 1;
bytes Data = 2;
}
cid.js
const mh = require('multihashes');
const axios = require('axios');
const crypto = require('crypto');
const protobuf = require("protobufjs");
const IPFS = protobuf.loadSync('./ipfs.proto').lookupType('PBNode');
class CID {
/**
* convert IPFS multihash to sha2-256 hash string
* #param {string} multihash
* #param {boolean} prefix
* #returns {string} sha2-256 hash string starting with 0x
*/
static toHash(multihash, prefix = false) {
return prefix ? '0x' : ''
+ mh.decode(mh.fromB58String(multihash)).digest.toString('hex')
}
/**
* convert sha2-256 hash string to IPFS multihash
* #param {string} str
* #returns {string} IPFS multihash starting with Qm
*/
static fromHash(str) {
str = str.startsWith('0x') ? str.slice(2) : str;
return mh.toB58String(mh.encode(Buffer.from(str, 'hex'), 'sha2-256'))
}
/**
* hash the buffer and get the SHA256 result compatible with IPFS multihash
* #param {Buffer} buf
* #returns {string}
*/
static hash(buf) {
const r = IPFS.encode({
Data: {
Type: 2,
Data: buf,
filesize: buf.length
}
}).finish();
return crypto.createHash('sha256').update(r).digest('hex');
}
}
async function ipfsGet(cid) {
const x = await axios.get(`http://your.address.xxx/ipfs/${cid}`, {
responseType: 'arraybuffer'
});
return Buffer.from(x.data);
}
const r = "QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN";
const hashFromCID = CID.toHash(r);
console.log(hashFromCID);
ipfsGet(r).then(buf => {
const hashCalculated = CID.hash(buf);
console.log(hashCalculated);
console.log(hashCalculated === hashFromCID);
console.log(CID.fromHash(hashCalculated) === r)
});
module.exports = CID;

I have created a module for Java which can generate the correct file hash:
https://github.com/rmnvalera/java-cid-generate-hash

Related

Encrypt nodejs data to mysql

I'm currently using Crypto to encrypt/ decrypt data, but, if the server restarts, the decrypt won't work anymore. That's what i'm currently using =>
const crypto = require("crypto");
const algorithm = "aes-256-cbc";
const initVector = crypto.randomBytes(16);
const Securitykey = crypto.randomBytes(32);
function encrypt(text){
const cipher = crypto.createCipheriv(algorithm, Securitykey, initVector);
let encryptedData = cipher.update(text, "utf-8", "hex");
encryptedData += cipher.final("hex");
return encryptedData;
}
function decrypt(text){
const decipher = crypto.createDecipheriv(algorithm, Securitykey, initVector);
let decryptedData = decipher.update(text, "hex", "utf-8");
decryptedData += decipher.final("utf8");
return decryptedData;
}
And this is the error I get if i want to decrypt something after server restart
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
So as I can see from the code your IV and Key are randomly generated and I am assuming that you are not saving them anywhere.
const initVector = crypto.randomBytes(16);
const Securitykey = crypto.randomBytes(32);
So basically on server restart you are getting a new pair of IV and key, so when you are decrypting it is not matching with the Key and IV used at the time of encryption.
My suggested solution :
const crypto = require("crypto");
const algorithm = "aes-256-cbc";
const initVectorString = "Any random hex string of 16bytes"; // You can store this into a env file
const SecuritykeyString = "Random security hex string of 32bytes"; // You can store this into a env file
const initVector = Buffer.from(initVectorString, "hex");
const Securitykey = Buffer.from(SecurityKeyString, "hex");
function encrypt(text){
const cipher = crypto.createCipheriv(algorithm, Securitykey, initVector);
let encryptedData = cipher.update(text, "utf-8", "hex");
encryptedData += cipher.final("hex");
return encryptedData;
}
function decrypt(text){
const decipher = crypto.createDecipheriv(algorithm, Securitykey, initVector);
let decryptedData = decipher.update(text, "hex", "utf-8");
decryptedData += decipher.final("utf8");
return decryptedData;
}
Update:-
So if you are using a utf-8 string for IV then the string length should be 16 characters only (if you are using only 1 byte characters a-zA-Z0-9 all are 1 byte characters) and you need to change the encoding type in Buffer.from() function from "Hex" to "utf-8".
Similar for the security key length of the string should be 32 characters only and you need to change the encoding type in Buffer.from() function from "Hex" to "utf-8".

Using a string in JSON format in Terraform variables

I am creating some resources using the for_each method on Terraform version 0.14.15. The resource has an attirbute, input_parameters that takes a string in JSON format as its value. I am defining this value in a map variable utilizing separate objects. The value I am specifying as a string in JSON format, and I am getting an error upon execution that I need to declare a string. Any insight on fixing this error would be helpful. Below is how I have my resource and variable declared.
Resource
resource "aws_config_config_rule" "managed_rules" {
for_each = var.managed_rules
name = each.value.name
description = each.value.description
input_parameters = each.value.input_parameters
source {
owner = each.value.owner
source_identifier = each.value.source_identifier
}
depends_on = [aws_config_configuration_recorder.config_recorder]
}
Variable
variable "managed_rules" {
type = map(object({
name = string
description = string
owner = string
source_identifier = string
# Is there a variable for strings in JSON format?
input_parameters = string
}))
default = {
"1" = {
name = "alb-http-to-https-redirection-check"
description = "Checks whether HTTP to HTTPS redirection is configured on all HTTP listeners of Application Load Balancers. The rule is NON_COMPLIANT if one or more HTTP listeners of Application Load Balancer do not have HTTP to HTTPS redirection configured."
owner = "AWS"
source_identifier = "ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK"
input_parameters = {
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
}
Error
This default value is not compatible with the variable's type constraint:
element "2": attribute "input_parameters": string required.
After updating code with jsonencode function and changing input_parameters to any, this is the error:
This default value is not compatible with the variable's type constraint:
collection elements cannot be unified.
You have a couple things going on here:
The resource requires input_parameters to be a JSON-encoded string
You have the variable type as a string
You're passing an object type into the variable that only accepts a string type
So (2) and (3) are conflicting. At some point, you have to convert your object into a JSON string. You can either do that before passing it in as an input variable, or change your input variable to accept objects and convert the object to JSON when providing it to the resource.
I'd choose the second option because it's more intuitive to pass the object into the module instead of a string. So, try this:
resource "aws_config_config_rule" "managed_rules" {
for_each = var.managed_rules
name = each.value.name
description = each.value.description
input_parameters = jsonencode(each.value.input_parameters)
source {
owner = each.value.owner
source_identifier = each.value.source_identifier
}
depends_on = [aws_config_configuration_recorder.config_recorder]
}
variable "managed_rules" {
type = map(object({
name = string
description = string
owner = string
source_identifier = string
# Is there a variable for strings in JSON format?
input_parameters = any
}))
default = {
"1" = {
name = "alb-http-to-https-redirection-check"
description = "Checks whether HTTP to HTTPS redirection is configured on all HTTP listeners of Application Load Balancers. The rule is NON_COMPLIANT if one or more HTTP listeners of Application Load Balancer do not have HTTP to HTTPS redirection configured."
owner = "AWS"
source_identifier = "ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK"
input_parameters = {
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
}
Note that I've used jsonencode in the resource's input_parameters and I've changed the variable type for that field to any (so it will accept an object of any structure).
You can create your json string as follows:
variable "managed_rules" {
type = map(object({
name = string
description = string
owner = string
source_identifier = string
# Is there a variable for strings in JSON format?
input_parameters = string
}))
default = {
"1" = {
name = "alb-http-to-https-redirection-check"
description = "Checks whether HTTP to HTTPS redirection is configured on all HTTP listeners of Application Load Balancers. The rule is NON_COMPLIANT if one or more HTTP listeners of Application Load Balancer do not have HTTP to HTTPS redirection configured."
owner = "AWS"
source_identifier = "ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK"
input_parameters = <<EOL
{
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
EOL
}
}
}
But then you have to use jsondecode if you want to parse this string. You can't use functions in variables, so it must be done later.
To add to Jordan's answer. I Had a similar concern when trying to add a json policy to a module.
I used the any object type in place of the string object type.
Here's how I fixed it:
Module main.tf
resource "aws_ecr_repository_policy" "main" {
repository = var.repository_name
policy = var.repository_policy
}
Module variables.tf
variable "repository_name" {
type = string
description = "Name of the repository."
}
variable "repository_policy" {
type = any
description = "The policy document. This is a JSON formatted string."
}
Resource Creation main.tf
# ECR Repository for container images
module "ecr_repository_1" {
source = "../../../../modules/aws/ecr-repository"
ecr_repository_name = var.ecr_repository_name.1
image_tag_mutability = var.image_tag_mutability
image_scan_on_push = var.image_scan_on_push
tag_environment = local.tag_environment
tag_terraform = local.tag_terraform.true
}
# ECR Repository policies
module "ecr_repository_policy_1" {
source = "../../../../modules/aws/ecr-repository-policy"
repository_name = var.ecr_repository_name.1
repository_policy = var.repository_policy.1
}
Resource creation variables.tf
variable "ecr_repository_name" {
type = map(string)
description = "Name of the repository."
default = {
"1" = "my-backend-api"
}
}
variable "image_tag_mutability" {
type = string
description = "The tag mutability setting for the repository. Must be one of: MUTABLE or IMMUTABLE. Defaults to MUTABLE."
default = "MUTABLE"
}
variable "image_scan_on_push" {
type = bool
description = "Indicates whether images are scanned after being pushed to the repository (true) or not scanned (false)."
default = true
}
variable "repository_policy" {
type = any
description = "The policy document. This is a JSON formatted string."
default = {
"1" = <<EOF
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "new policy",
"Effect": "Allow",
"Principal": "*",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:DescribeRepositories",
"ecr:GetRepositoryPolicy",
"ecr:ListImages",
"ecr:DeleteRepository",
"ecr:BatchDeleteImage",
"ecr:SetRepositoryPolicy",
"ecr:DeleteRepositoryPolicy"
]
}
]
}
EOF
}
}
input_parameters = {
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
This has to be a string instead of Object, Since you defined it as String

Esp 8266 parse json async

I'm using asyncHTTPrequest for async request to a REST API in ESP8266.
I receive the response in JSON format but can't parse it.
This kind of parsing was working while i used to made sync call to API.
I tried to store the request->responseText() into a String variable because its return a String, but the variable never get any value.
void sendRequest() {
if (request.readyState() == 0 || request.readyState() == 4) {
request.open("GET", "http://192.168.1.103:45456/api/systems/1013/arduino");
request.send();
}
}
void requestCB(void* optParm, asyncHTTPrequest* request, int readyState) {
if (readyState == 4) {
Serial.println(request->responseText());
const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(2) + JSON_OBJECT_SIZE(2) + 2*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(8)+816;
DynamicJsonBuffer jsonBuffer(capacity);
JsonObject& root = jsonBuffer.parseObject(request->responseText());
String a = request->responseText();
Serial.println(a);
JsonObject& schState = root["dataForArduino"][0];
String beginTime = schState["start"]; // "2019-12-02T21:51:00"
}
}
void setup() {
Serial.begin(9600);
wifi.Connect();
request.onReadyStateChange(requestCB);
ticker.attach(5, sendRequest);
}
I have wrote json parsing function (__get_from_json) to get key value from json here
e.g. if you have json response like
{
"timestamp" : "2020-04-01 19:20:49"
}
and in your application you want to parse timestamp value from it then
char response[max_response_size] = "{ \"timestamp\" : \"2020-04-01 19:20:49\" }";
char key[max_key_size] = "timestamp";
char value[max_value_size] = "";
if( __get_from_json( response, key, value, max_value_size ) ){
Serial.println(value);
}
I had the same problem and added .c_str() to get the response to print.
So in your example it would be:
String a = request->responseText();
Serial.println(a.c_str());
For the JSON I also needed to add .c_str()
DynamicJsonDocument jsonDoc(2048);
DeserializeJson(jsonDoc, a.c_str());

How to send a POST with a JSON in a WebRequest() call using MQL4?

I would like to send a POST from MQL4-script, using a JSON-format to a Node-server.
I've tried the webRequest() standard function in MQL4, based on the following documentation, but it did NOT success.
From MQL4 Documentation:
Sending simple requests of type "key=value" using the header `Content-Type: application/x-www-form-urlencoded`.
int WebRequest( const string method, // HTTP method
const string url, // URL
const string cookie, // cookie
const string referer, // referer
int timeout, // timeout
const char &data[], // the array of the HTTP message body
int data_size, // data[] array size in bytes
char &result[], // an array containing server response data
string &result_headers // headers of server response
);
and
Sending a request of any type specifying the custom set of headers for a more flexible interaction with various Web services.
int WebRequest( const string method, // HTTP method
const string url, // URL
const string headers, // headers
int timeout, // timeout
const char &data[], // the array of the HTTP message body
char &result[], // an array containing server response data
string &result_headers // headers of server response
);
Parameters
method [in] HTTP method.
url [in] URL.
headers [in] Request headers of type "key: value", separated by a line break "\r\n".
cookie [in] Cookie value.
referer [in] Value of the Referer header of the HTTP request.
timeout [in] Timeout in milliseconds.
data[] [in] Data array of the HTTP message body.
data_size [in] Size of the data[] array.
result[] [out] An array containing server response data.
result_headers [out] Server response headers.
Returned value:
HTTP server response code or -1 for an error.
Does anyone know how to perfom it?
UPDATE:
Here is the code on the MQL4-script side :
#include <Json\hash.mqh>
#include <Json\KoulJSONMgmt.mqh>
void OnStart()
{
string strParam = StringConcatenate("{","\"currency\"",":","\"",Symbol(),"\"",",","\"timeframe\"",":","\"",IntegerToString(Period()),"\"",",","\"ticktime\"",":","\"",TimeToString(TimeLocal(),TIME_DATE|TIME_SECONDS),"\"",",","\"bid\"",":",DoubleToString(MarketInfo(Symbol(),MODE_BID),4),",","\"ask\"",":",DoubleToString(MarketInfo(Symbol(),MODE_ASK),4),",","\"spread\"",":",DoubleToString(MarketInfo(Symbol(),MODE_SPREAD),0),"}");
JSONParser *parser = new JSONParser();
JSONValue *jv = parser.parse(strParam);
string strJson = jv.toString();
if (jv == NULL) {
Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage());
} else {
Print("PARSED:"+strJson);
//Example of json String :
//EURUSD,M15: PARSED:{"bid" : 1.1152,"ask" : 1.1154,"spread" : 13,"ticktime" : "2016.10.10 16:24:01","currency" : "EURUSD","timeframe" : "15"}
}
string cookie=NULL,headers;
char post[],result[];
int res;
string strResult,result_header;
headers = "application/json";
prmUrl=StringConcatenate("http://localhost/api"+"/"+"ticks");
//--- Reset the last error code
ResetLastError();
int timeout=1000; //--- Timeout below 1000 (1 sec.) is not enough for slow Internet connection
int intHostNameLength=StringLen(prmParameter);
StringToCharArray(prmParameter,post,0,intHostNameLength);
res=WebRequest("POST",prmUrl,headers,timeout,post,result,result_header);
//--- Checking errors
if(res==-1)
{
Print("Error in WebRequest. Error code =",GetLastError());
//--- Perhaps the URL is not listed, display a message about the necessity to add the address
Print("Add the address '"+prmUrl+"' in the list of allowed URLs on tab 'Expert Advisors'","Error",MB_ICONINFORMATION);
}
else
{
for(int i=0;i<ArraySize(result);i++)
{
if( (result[i] == 10) || (result[i] == 13)) {
continue;
} else {
strResult += CharToStr(result[i]);
}
}
ArrayCopy(strResult,result,0,0,WHOLE_ARRAY);
}
Print(strResult);
}
And the Node side is :
server.js
//Create new Tick
app.post('/api/ticks', function(req, res) {
console.log('Inserting New Tick');
var tick = req.body;
console.log('>'+JSON.stringify(tick,null,4));
Tick.addTick(tick, function(err, tick){
if(err) res.json(err);
res.json(tick);
});
});
and in model ticks.js
var mongoose = require('mongoose');
// User Schema
var TickSchema = mongoose.Schema({
currency:{
type: String
},
timeframe: {
type: String
},
ticktime: {
type: Date
},
bid: {
type: Number
},
ask: {
type: Number
},
spread: {
type: Number
},
createddate :{
type: Date,
default: Date.now
}
}, {collection : 'fxTicks'});
var Tick = module.exports = mongoose.model('Tick', TickSchema);
//Create New Tick
module.exports.addTick = function(tick, callback){
Tick.create(tick, callback);
};
// Get Ticks
module.exports.getTicks = function(callback, limit){
Tick.find(callback).limit(limit);
};
So, back to the square No.1:
In the last-year's post, there was a step by step methodology to proceed with a MCVE-based approach to the problem isolation.
Repeating the same steps here, inside MQL4-code,
adding a python-based mock-up WebSERVER, to diagnose the actual working client/server http-protocol exchange & handshaking, ( not the WebSERVER-side interpretation of the delivered POST-request, which is the same, as if one have launched the URL from a WebBROWSER, for all related details ref: BaseHTTPServer.BaseHTTPRequestHandler )
>>> import BaseHTTPServer
>>> server_class = BaseHTTPServer.HTTPServer
>>> handler_class = BaseHTTPServer.BaseHTTPRequestHandler
>>> httpd = server_class( ( '', 8765 ), handler_class )
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 09:46:45] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 09:46:45] "GET /?test=123_from_Chrome HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 09:47:23] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 09:47:23] "GET /favicon.ico HTTP/1.1" 501 -
>>>
>>>
>>>
>>> httpd = server_class( ( '', 80 ), handler_class )
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 10:22:05] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 10:22:05] "GET /?test=123_from_Chrome_on_port_80 HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 10:22:31] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 10:22:31] "GET /?test=123_from_Chrome_on_port_80_again HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 10:22:34] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 10:22:34] "GET /favicon.ico HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 11:25:56] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 11:26:12] "GET /?test=123_from_Chrome_on_port_80_another_call HTTP/1.1" 501 -
>>>
>>>
127.0.0.1 - - [10/Oct/2016 12:03:03] code 501, message Unsupported method ('POST')
127.0.0.1 - - [10/Oct/2016 12:03:03] "POST / HTTP/1.1" 501 -
>>>
the output isproviding an evidence that the last pair of rows were produced by an MQL4-side WebRequest() that was setup correctly and works fine there and back[ MetaTrader Terminal 4 ]-Log reads:
2016.10.10 12:03:03.921 ___StackOverflow_WebRequest_DEMO XAUUSD,H1:
DATA:: <head><title>Error response</title></head>
<body>
<h1>Error response</h1><p>Error code 501.<p>
Message: Unsupported method ('POST').<p>
Error code explanation: 501 = Server does not support this operation.
</body>
2016.10.10 12:03:03.921 ___StackOverflow_WebRequest_DEMO XAUUSD,H1:
HDRs:: HTTP/1.0 501 Unsupported method ('POST')
Server: BaseHTTP/0.3 Python/2.7.6
Date: Mon, 10 Oct 2016 20:03:03 GMT
Content-Type: text/html
Connection: close
A raw MQL4-snippet BUT use at one's own risk! ( strongly encourage NOT to use any BLOCKING WebRequest() callsin any Production-grade code...for NON-BLOCKING tools see my other posts and how to or read into internal details on high-performance, low-latency, non-blocking integration tools for distributed heterogeneous systems processing alike ZeroMQ or nanomsg )
All have been warned, so:
Last years setup picture is still valid:
The mock-up WebSERVER had inside the dotted form-field input of:
http://localhost/
One shall also bear in mind, that trying to set a specific port designation in the URL will violate the MetaQuotes Inc. design rule, that a port is being derived from the protocol pragma at the beginning of the URL declaration, so:
http://localhost:8765/
will not work, as MQL4 WebRequest() CANNOT use other port but either of { 80 | 443 }, given by protocol pragma stated in URL: { http: | https: }
Thus for any port-numbering gymnastics, one has to setup and tune a proper set of port-forwarding services, that would leave MetaTrader Terminal 4 live inside this design-cage, using just either of { 80 | 443 }.
The simplest MQL4-script OnStart() demonstrator looks this way:
//+------------------------------------------------------------------+
//| ___StackOverflow_WebRequest_DEMO.mq4 |
//| Copyright © 1987-2016 [MS] |
//| nowhere.no |
//+------------------------------------------------------------------+ >>> https://stackoverflow.com/questions/39954177/how-to-send-a-post-with-a-json-in-a-webrequest-call-using-mql4
#property copyright "Copyright © 1987-2016 [MS]"
#property link "nowhere.no"
#property version "1.00"
#property strict
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart(){
/* A BRIGHTER WAY:
string JSON_string = StringFormat( "{\"currency\": \"%s\", \"timeframe\": \"%d\", \"ticktime\": \"%s\", \"bid\": %f, \"ask\": %f, \"spread\": %s }", _Symbol,
Period(),
TimeToString( TimeLocal(), TIME_DATE | TIME_SECONDS ),
MarketInfo( _Symbol, MODE_BID ),
MarketInfo( _Symbol, MODE_ASK ),
MarketInfo( _Symbol, MODE_SPREAD )
);
// A SMARTER WAY & THE FASTEST PROCESSING TIMES:
// --------------
#define MQL4_COMPILE_TIME_JSON_TEMPLATE "{\"currency\": \"%s\", \"timeframe\": \"%d\", \"ticktime\": \"%s\", \"bid\": %f, \"ask\": %f, \"spread\": %s }" // CONSTANT TEMPLATE TO FILL-IN AD-HOC VALUES:
// +
string JSON_string = StringFormat( MQL4_COMPILE_TIME_JSON_TEMPLATE", _Symbol,
Period(),
TimeToString( TimeLocal(), TIME_DATE | TIME_SECONDS ),
MarketInfo( _Symbol, MODE_BID ),
MarketInfo( _Symbol, MODE_ASK ),
MarketInfo( _Symbol, MODE_SPREAD )
);
*/
string JSON_string = StringConcatenate( "{", // **** MQL4 can concat max 63 items
"\"currency\"",
":",
"\"",
Symbol(),
"\"",
",",
"\"timeframe\"",
":",
"\"",
IntegerToString( Period() ),
"\"",
",",
"\"ticktime\"",
":",
"\"",
TimeToString( TimeLocal(), TIME_DATE | TIME_SECONDS ),
"\"",
",",
"\"bid\"",
":",
DoubleToString( MarketInfo( Symbol(), MODE_BID ), 4 ),
",",
"\"ask\"",
":",
DoubleToString( MarketInfo( Symbol(), MODE_ASK ), 4 ),
",",
"\"spread\"",
":",
DoubleToString( MarketInfo( Symbol(), MODE_SPREAD ), 0 ),
"}"
);
// */
/* off-topic: a JSON-string VALIDATOR -----------------------------------------------------------------------------------------------------------------------------------
#include <Json\hash.mqh>
#include <Json\KoulJSONMgmt.mqh>
JSONParser *parser = new JSONParser();
JSONValue *jv = parser.parse(strParam);
string strJson = jv.toString();
if ( jv == NULL ) Print( "ERROR:" + (string) parser.getErrorCode()
+ parser.getErrorMessage()
);
else Print( "PARSED:" + strJson );
// Example of a journalled Print() for an above setup JSON String :
// EURUSD,M15: PARSED:{"bid" : 1.1152,"ask" : 1.1154,"spread" : 13,"ticktime" : "2016.10.10 16:24:01","currency" : "EURUSD","timeframe" : "15"}
*/ // off-topic: a JSON-string VALIDATOR -----------------------------------------------------------------------------------------------------------------------------------
// string ReqSERVER_URL = "http://localhost:8765/", // **** MQL4 WebRequest CANNOT use other port but either of { 80 | 443 } given by protocol pragma stated in URL: { http: | https: }
string ReqSERVER_URL = "http://localhost/", // ---- MQL4 WebRequst
ReqCOOKIE = NULL,
// ReqHEADERs = "application/json"; // **** MQL4 WebRequest MUST use [in] Request headers of type "key: value", separated by a line break "\r\n".
ReqHEADERs = "Content-Type: application/json\r\n";
int ReqTIMEOUT = 5000; // ---- MQL4 WebRequest SHALL use [in] Timeouts below 1000 (1 sec.) are not enough for slow Internet connection;
// ================================================= // ~~~~ MQL4 WebRequest SHALL be AVOIDED as an un-control-able BLOCKING-SHOW-STOPPER, any professional need shall use NON-BLOCKING tools
char POSTed_DATA[],
result_RECVed_DATA_FromSERVER[];
int result_RetCODE;
string result_DecodedFromSERVER,
result_RECVed_HDRs_FromSERVER;
// int intHostNameLength = StringLen( ReqSERVER_URL );
// StringToCharArray( ReqSERVER_URL, POSTed_DATA, 0, StringLen( ReqSERVER_URL ) );
// StringToCharArray( prmParameter, post, 0, intHostNameLength );
StringToCharArray( JSON_string, POSTed_DATA, 0, StringLen( JSON_string ) );
ResetLastError();
result_RetCODE = WebRequest( "POST",
ReqSERVER_URL,
ReqHEADERs,
ReqTIMEOUT,
POSTed_DATA,
result_RECVed_DATA_FromSERVER,
result_RECVed_HDRs_FromSERVER
);
if ( result_RetCODE == -1 ) Print( "Error in WebRequest. Error code =", GetLastError() ); // returns error 4060 – "Function is not allowed for call" unless permitted -- ref. Picture in >>> https://stackoverflow.com/questions/39954177/how-to-send-a-post-with-a-json-in-a-webrequest-call-using-mql4
else {
for ( int i = 0; i < ArraySize( result_RECVed_DATA_FromSERVER ); i++ ) {
if ( ( result_RECVed_DATA_FromSERVER[i] == 10 ) // == '\n' // <LF>
|| ( result_RECVed_DATA_FromSERVER[i] == 13 ) // == '\r' // <CR>
)
continue;
else result_DecodedFromSERVER += CharToStr( result_RECVed_DATA_FromSERVER[i] );
}
Print( "DATA:: ", result_DecodedFromSERVER );
Print( "HDRs:: ", result_RECVed_HDRs_FromSERVER );
}
}
//+------------------------------------------------------------------+
Deviations from documented steps are easily visible in the source and were left for clarity.
Epilogue:
If documentation says something, it is worth keeping that advice ( with some tests, sure ).
If a sponsored Community advice says something, it is worth giving it at least a try, before asking for more.
It works well for me, I have someone like this :
string response = SendResquest("POST", "GetPrediction", "[4, 7]", "Content-Type: application/json", 5000);
string SendResquest(string httpType, string methodName, string bodyData = "", string headers = "", int timeout)
{
uchar bodyDataCharArray[];
ArrayResize(bodyDataCharArray, StringToCharArray(bodyData, bodyDataCharArray)-1);
int response = WebRequest(httpType, this.address+methodName, headers, timeout, bodyDataCharArray, this.resultDataCharArray, this.resultHeader);
string result = CharArrayToString(this.resultDataCharArray);
if(response == 200)
return result;
Print("Error when trying to call API : ", response);
return "";
}
Allow your [ MetaTrader Terminal 4 ] to communicate with URL via menu:
Tools -> Options -> Expert Advisors ->
1. mark a check-box [ X ] in front of 'Allow WebReq....'
&
2. type the URL name below the check-box, using the green (+) icon, inside the form.
If this doesn't help - try to add Print() statements to see the possible errors ( incorrect MQL4 code or incorrect JSON-format file ).
Have you tried setting the headers like this:
headers = "Content-Type: application/json\r\n";
?

What kind of data does Yahoo! Messenger API return for requests?

I looked in the Yahoo! Messenger API documentation to see how can I get the access token and I found this:
The call looks like this:
https://login.yahoo.com/WSLogin/V1/get_auth_token?&login=username&passwd=mypassword&oauth_consumer_key=consumerkey
The result of this call is a single value, the RequestToken:
RequestToken=jUO3Qolu3AYGU1KtB9vUbxlnzfIiFRLP...
This token is then used for a second request, which exchanges the PART for an OAuth access token. You can find out more information on the standard method getting an access token here.
I guess that the result is a standard result, but I don't know what kind of data is this. I mean that it isn't XML or JSON.
I would like to convert such a string to JSON:
{
RequestToken: "jU0..."
}
Is there any standard converter/parser or must I build one?
Also, another request can look like below:
Error=MissingParameters
ErrorDescription=Sorry, try again with all the required parameters.
and I want to convert it into JSON:
{
Error: "MissingParameters",
ErrorDescription: "Sorry, try again with all the required parameters."
}
It would be very easy to build such a parser, but I don't want to reinvent the wheel.
I decided to write my own function. If there is a standard algorithm to parse such a string leave a comment.
/*
* Transform a string like this:
*
* "Field1=123
* Field2=1234
* Field3=5"
*
* into an object like this:
*
* {
* "Field1": "123",
* "Field2": "1234",
* "Field3": "5",
* }
*
* */
function parseResponse (str) {
// validate the provided value
if (typeof str !== "string") {
throw new Error("Please provide a string as argument: " +
JSON.stringify(str));
}
// split it into lines
var lines = str.trim().split("\n");
// create the object that will be returned
var parsedObject = {};
// for every line
for (var i = 0; i < lines.length; ++i) {
// split the line
var splits = lines[i].split("=")
// and get the field
, field = splits[0]
// and the value
, value = splits[1];
// finally set them in the parsed object
parsedObject[field] = value;
}
// return the parsed object
return parsedObject;
};