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

19,085

Solution 1

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 is
providing 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() calls
in 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: enter image description here

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.

Solution 2

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 "";
}

Solution 3

Have you tried setting the headers like this:

headers = "Content-Type: application/json\r\n";

?

Share:
19,085
koul
Author by

koul

Updated on July 18, 2022

Comments

  • koul
    koul almost 2 years

    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);
    };