
Implementing Practical Modules from Other Languages in MQL5 (Part 02): Building the REQUESTS Library, Inspired by Python
Contents
- Introduction
- Making a web request
- Different web requests in different functions
- Uploading files to the web
- Receiving and downloading files from the web
- Session and Cookies handling
- Basic authentication
- Dealing with URL parameters
- Conclusion
Introduction
An ability to send HTTP requests to the web directly from MetaTrader 5 is one of the best things that's ever happened to the MQL5 programming language. With this ability, traders can communicate with their external websites, servers, trading apps, etc.
This makes us capable of doing almost everything inside the trading platform, like getting data from external sources, sending trading notifications to our peers, and much more.
This ability has been made possible by the function WebRequest available in MQL5, which enables us to perform any HTTP action such as:
- Sending "POST" requests for sending information to external servers.
- Getting information from the web using the famous "GET" request.
- Sending PATCH requests to the web for modifying information from the server's database.
- Sending PUT requests to the web for updating values present in the server's database.
To name a few HTTP actions.
However, this single function can be overwhelming sometimes, and it's not user-friendly.
It takes a lot of work to send a simple web request to perform any of the actions mentioned above, and more as you have to worry about HTTP methods, headers, and much more. Not to mention, you need to hardcode the process of handling the data you want to send or receive.
If you've ever used a web framework or modules for similar tasks in your coding career, you might notice that most frameworks outside MQL5 are equipped to handle most of the basic tasks and operations that this WebRequest function in MQL5 doesn't.
One of the modules is requests from Python programming language.
The Requests module — dubbed as HTTP for Humans:
Is a simple, yet elegant, HTTP library that allows programmers to send HTTP/1.1 requests extremely easily.
Using this library, Python developers don't need to manually add query strings in URLs or to form-encode PUT and POST data, and much more as everything is well crafted and simplified even for the "non-techy" developers.
In this article, we are going to implement a similar module with similar functions, syntax, and capabilities. Hoping to make it easy and convenient to perform webrequests in MQL5 as in Python.
Making a Web Request
This is the main functionality in the requests module. The request function is the one that sends and receives stuff and information in different formats from the web.
Before mimicking a similar method offered in the requests module in Python, let's take a look at this function first.
The request method in Python looks like the following.
def request( method: str | bytes, url: str | bytes, *, params: _Params | None = ..., data: _Data | None = ..., headers: _HeadersMapping | None = ..., cookies: CookieJar | _TextMapping | None = ..., files: _Files | None = ..., auth: _Auth | None = ..., timeout: _Timeout | None = ..., allow_redirects: bool = ..., proxies: _TextMapping | None = ..., hooks: _HooksInput | None = ..., stream: bool | None = ..., verify: _Verify | None = ..., cert: _Cert | None = ..., json: Any | None = None ) -> Response
This function takes a couple of arguments for the HTTP web request. Let's start by building it with a few parameters for now: The method, url, data, headers, timeout, and json parameters.
CResponse CSession::request(const string method, //HTTP method GET, POST, etc const string url, //endpoint url const string data = "", //The data you want to send if the method is POST or PUT const string headers = "", //HTTP headers const int timeout = 5000, //Request timeout (milliseconds) const bool is_json=true) //Checks whether the given data is in JSON format { char data_char[]; char result[]; string result_headers; string temp_data = data; CResponse response; //a structure containing various response fields //--- Managing the headers string temp_headers = m_headers; if (headers != "") // If the user provided additional headers, append them temp_headers += headers; //--- Updating the headers with the information received if(is_json) //If the information parsed is { //--- Convert dictionary to JSON string CJAVal js(NULL, jtUNDEF); bool b = js.Deserialize(data, CP_UTF8); string json; js.Serialize(json); //Get the serialized Json outcome temp_data = json; //Assign the resulting serialized Json to the temporary data array //--- Set "Content-Type: application/json" temp_headers = UpdateHeader(temp_headers, "Content-Type", "application/json"); if (MQLInfoInteger(MQL_DEBUG)) printf("%s: %s",__FUNCTION__,temp_headers); } else { temp_headers = UpdateHeader(temp_headers, headers); if (MQLInfoInteger(MQL_DEBUG)) printf("%s: %s",__FUNCTION__,temp_headers); } //--- Convert data to byte array (for POST, PUT, etc.) if (StringToCharArray(temp_data, data_char, 0, StringLen(temp_data), CP_UTF8)<0) //Convert the data in a string format to a uchar { printf("%s, Failed to convert data to a Char array. Error = %s",__FUNCTION__,ErrorDescription(GetLastError())); return response; } //--- Perform the WebRequest uint start = GetTickCount(); //starting time of the request int status = WebRequest(method, url, temp_headers, timeout, data_char, result, result_headers); //trigger a webrequest function if(status == -1) { PrintFormat("WebRequest failed with error %s", ErrorDescription(GetLastError())); response.status_code = 0; return response; } //--- Fill the response struct response.elapsed = (GetTickCount() - start); string results_string = CharArrayToString(result); response.text = results_string; CJAVal js; if (!js.Deserialize(result)) if (MQLInfoInteger(MQL_DEBUG)) printf("Failed to serialize data received from WebRequest"); response.json = js; response.cookies = js["cookies"].ToStr(); response.status_code = status; ArrayCopy(response.content, result); response.headers = result_headers; response.url = url; response.ok = (status >= 200 && status < 400); response.reason = WebStatusText(status); // a custom helper for translating status codes return response; }
The request function in Python gives us two options for passing the data to a webrequest using two different arguments; the data argument for passing all the non-JSON data and the json argument for passing the JSON data.
Since both arguments provide the information to be fed to the request, and only one argument can be used for sending the data, i.e,. You either send raw data (HTML, Plain text, etc) or the JSON formatted data.
So, the function in Python detects the type of given data (JSON or otherwise) and adjusts its headers according to the headers given by the user. For example, it appends or modifies the header to Content-Type: application/json when the JSON data is given.
While we could also have both arguments in the MQL5 function as well, I find the idea so confusing and adds unnecessary complexity. So the function in our MQL5 class takes only one argument named data for sending data to the web, the boolean argument named is_json is the one responsible for distinguishing the information received in the variable named data (between JSON and other types) inside the function.
Again, when the argument is_json is set to true the function appends or modifies the received headers with the value (Content-Type: application/json). But, before that, the received data from the argument data is serialized to the right JSON format before sending it to the web.
//--- Updating the headers with the information received if(is_json) //If the information parsed is { //--- Convert dictionary to JSON string CJAVal js(NULL, jtUNDEF); bool b = js.Deserialize(data, CP_UTF8); string json; js.Serialize(json); //Get the serialized Json outcome temp_data = json; //Assign the resulting serialized Json to the temporary data array //--- Set "Content-Type: application/json" temp_headers = UpdateHeader(temp_headers, "Content-Type", "application/json"); if (MQLInfoInteger(MQL_DEBUG)) printf("%s: %s",__FUNCTION__,temp_headers); } else { temp_headers = UpdateHeader(temp_headers, headers); if (MQLInfoInteger(MQL_DEBUG)) printf("%s: %s",__FUNCTION__,temp_headers); }
The request method offered in the requests module in Python, returns a plenty of variables according to the HTTP Response; containing information about the state of the request, data received, errors, and much more.
import requests r = requests.get('https://api.github.com/events') # Print the raw response print("Raw response:", r) # Status code print("Status Code:", r.status_code) # Reason phrase print("Reason:", r.reason) # URL (final URL after redirects) print("URL:", r.url) # Headers (dictionary) print("Headers:") #... other responses
To achieve this, we need a similar structure in our MQL5 class.
Inside the file requests.mqh, below is the CResponse class.
struct CResponse { int status_code; // HTTP status code (e.g., 200, 404) string text; // Raw response body as string CJAVal json; // Parses response as JSON uchar content[]; // Raw bytes of the response string headers; // Dictionary of response headers string cookies; // Cookies set by the server string url; // Final URL after redirects bool ok; // True if status_code < 400 uint elapsed; // Time taken for the response in ms string reason; // Text reason (e.g., "OK", "Not Found") };
This is the structure that the function request returns inside the CSession class — we'll discuss it in a second.
For the record, below is the tabulated list of all variables and information they hold inside the CResponse class.
Variable | Datatype | Description |
---|---|---|
status_code | int | HTTP status code returned by the server (e.g., 200=OK, 404 = Not Found, etc,.) |
text | string | The full response body as a raw string (e.g., HTML, JSON, or text) |
json | CJAVal | A Parsed JSON object from the response body, if serialization process was a success. |
content[] | uchar | Raw byte array of the response body (useful for binary responses). |
headers | string | A dictionary containing HTTP response headers. This can be converted to JSON if you want. |
cookies | string | Cookies set by the server (extracted from Set-cookie headers, if any). |
url | string | The final URL after any redirects. |
ok | bool | Becomes true if the status code is < 400, i.e., no client/server errors occurred in the process. |
elapsed | uint | The time in milliseconds taken to complete the request |
reason | string | HTTP status code in text format — human readable. |
Below is how to use the request function.
For this example to work on your machine make sure you add https://httpbin.org (a testing URL) to the list of allowed URLs in MetaTrader 5.
(a): Sending the JSON data
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string json = "{\"username\": \"omega\", \"password\": \"secret123\"}"; CResponse response = CSession::request("POST","https://httpbin.org/post", json, NULL, 5000, true); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (string)response.ok); Print("Elapsed Time (ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }
Outputs.
JF 0 08:10:33.226 Requests test (XAUUSD,D1) CSession::request: Content-Type: application/json GF 0 08:10:33.226 Requests test (XAUUSD,D1) MI 0 08:10:34.578 Requests test (XAUUSD,D1) Status Code: 200 PS 0 08:10:34.578 Requests test (XAUUSD,D1) Reason: OK LH 0 08:10:34.578 Requests test (XAUUSD,D1) URL: https://httpbin.org/post HP 0 08:10:34.578 Requests test (XAUUSD,D1) OK: true IF 0 08:10:34.578 Requests test (XAUUSD,D1) Elapsed Time (ms): 1343 HO 0 08:10:34.578 Requests test (XAUUSD,D1) Headers: QE 0 08:10:34.578 Requests test (XAUUSD,D1) Date: Fri, 04 Jul 2025 05:10:35 GMT MG 0 08:10:34.578 Requests test (XAUUSD,D1) Content-Type: application/json HQ 0 08:10:34.578 Requests test (XAUUSD,D1) Content-Length: 619 IH 0 08:10:34.578 Requests test (XAUUSD,D1) Connection: keep-alive JL 0 08:10:34.578 Requests test (XAUUSD,D1) Server: gunicorn/19.9.0 QD 0 08:10:34.578 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * IO 0 08:10:34.578 Requests test (XAUUSD,D1) Access-Control-Allow-Credentials: true PI 0 08:10:34.578 Requests test (XAUUSD,D1) GO 0 08:10:34.578 Requests test (XAUUSD,D1) Cookies: HH 0 08:10:34.578 Requests test (XAUUSD,D1) Response: { QL 0 08:10:34.578 Requests test (XAUUSD,D1) "args": {}, FK 0 08:10:34.578 Requests test (XAUUSD,D1) "data": "{\"username\":\"omega\",\"password\":\"secret123\"}", MN 0 08:10:34.578 Requests test (XAUUSD,D1) "files": {}, RH 0 08:10:34.578 Requests test (XAUUSD,D1) "form": {}, OP 0 08:10:34.578 Requests test (XAUUSD,D1) "headers": { CH 0 08:10:34.578 Requests test (XAUUSD,D1) "Accept": "*/*", GM 0 08:10:34.578 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", HQ 0 08:10:34.578 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", GH 0 08:10:34.578 Requests test (XAUUSD,D1) "Content-Length": "43", IR 0 08:10:34.578 Requests test (XAUUSD,D1) "Content-Type": "application/json", EI 0 08:10:34.578 Requests test (XAUUSD,D1) "Host": "httpbin.org", NH 0 08:10:34.578 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", HK 0 08:10:34.578 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-6867624b-422e528808290adf61a651a3" IN 0 08:10:34.578 Requests test (XAUUSD,D1) }, GD 0 08:10:34.578 Requests test (XAUUSD,D1) "json": { CN 0 08:10:34.578 Requests test (XAUUSD,D1) "password": "secret123", KD 0 08:10:34.578 Requests test (XAUUSD,D1) "username": "omega" QM 0 08:10:34.578 Requests test (XAUUSD,D1) }, EJ 0 08:10:34.578 Requests test (XAUUSD,D1) "origin": "197.250.227.26", FQ 0 08:10:34.578 Requests test (XAUUSD,D1) "url": "https://httpbin.org/post" EG 0 08:10:34.578 Requests test (XAUUSD,D1) } PR 0 08:10:34.578 Requests test (XAUUSD,D1) NF 0 08:10:34.578 Requests test (XAUUSD,D1) JSON: CR 0 08:10:34.578 Requests test (XAUUSD,D1) https://httpbin.org/post
(b): Sending the non-JSON data
Using form data as an example.
#include <requests.mqh> CSession requests; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string form_data = "username=omega&password=secret123"; //mimicking how the final form data is collected in the web CResponse response = CSession::post("https://httpbin.org/post", form_data, "Content-Type: application/x-www-form-urlencoded", 5000, false); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (string)response.ok); Print("Elapsed Time (ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }
Outputs.
DD 0 08:20:01.411 Requests test (XAUUSD,D1) Status Code: 200 IN 0 08:20:01.411 Requests test (XAUUSD,D1) Reason: OK EG 0 08:20:01.411 Requests test (XAUUSD,D1) URL: https://httpbin.org/post QM 0 08:20:01.411 Requests test (XAUUSD,D1) OK: true RI 0 08:20:01.411 Requests test (XAUUSD,D1) Elapsed Time (ms): 1547 QR 0 08:20:01.411 Requests test (XAUUSD,D1) Headers: EH 0 08:20:01.411 Requests test (XAUUSD,D1) Date: Fri, 04 Jul 2025 05:20:02 GMT DL 0 08:20:01.411 Requests test (XAUUSD,D1) Content-Type: application/json MD 0 08:20:01.411 Requests test (XAUUSD,D1) Content-Length: 587 PM 0 08:20:01.411 Requests test (XAUUSD,D1) Connection: keep-alive OK 0 08:20:01.411 Requests test (XAUUSD,D1) Server: gunicorn/19.9.0 HS 0 08:20:01.411 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * PD 0 08:20:01.411 Requests test (XAUUSD,D1) Access-Control-Allow-Credentials: true IF 0 08:20:01.411 Requests test (XAUUSD,D1) RD 0 08:20:01.411 Requests test (XAUUSD,D1) Cookies: QM 0 08:20:01.411 Requests test (XAUUSD,D1) Response: { HK 0 08:20:01.411 Requests test (XAUUSD,D1) "args": {}, OR 0 08:20:01.411 Requests test (XAUUSD,D1) "data": "", JE 0 08:20:01.411 Requests test (XAUUSD,D1) "files": {}, RL 0 08:20:01.411 Requests test (XAUUSD,D1) "form": { DF 0 08:20:01.411 Requests test (XAUUSD,D1) "password": "secret123", PM 0 08:20:01.411 Requests test (XAUUSD,D1) "username": "omega" RE 0 08:20:01.411 Requests test (XAUUSD,D1) }, PL 0 08:20:01.411 Requests test (XAUUSD,D1) "headers": { DE 0 08:20:01.411 Requests test (XAUUSD,D1) "Accept": "*/*", LP 0 08:20:01.411 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", CF 0 08:20:01.411 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", EK 0 08:20:01.411 Requests test (XAUUSD,D1) "Content-Length": "33", EL 0 08:20:01.411 Requests test (XAUUSD,D1) "Content-Type": "application/x-www-form-urlencoded", PH 0 08:20:01.411 Requests test (XAUUSD,D1) "Host": "httpbin.org", GO 0 08:20:01.411 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", FQ 0 08:20:01.411 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-68676482-6439a0a071c275773681a193" LM 0 08:20:01.411 Requests test (XAUUSD,D1) }, JE 0 08:20:01.411 Requests test (XAUUSD,D1) "json": null, LM 0 08:20:01.411 Requests test (XAUUSD,D1) "origin": "197.250.227.26", KH 0 08:20:01.411 Requests test (XAUUSD,D1) "url": "https://httpbin.org/post" LN 0 08:20:01.411 Requests test (XAUUSD,D1) } IK 0 08:20:01.411 Requests test (XAUUSD,D1) CO 0 08:20:01.411 Requests test (XAUUSD,D1) JSON: NK 0 08:20:01.411 Requests test (XAUUSD,D1) https://httpbin.org/post
Awesome! We were able to send two distinct data types using the same Web request function.
Notice that, while sending the non-JSON data type (Form data in this case), I explicitly set "Content-type" in the headers argument of the request function to accommodate the form data.
So, unless you are sending the JSON data which is automatically serialized and the right HTTP header is automatically updated to accommodate this data type, you have to explicitly set the Content-type to accommodate the kind of data you want to send.
For more information read -> https://beeceptor.com/docs/concepts/content-type/index.html
Now, this request function is capable of sending all kinds of HTTP requests similarly to the native MQL5 function, This is an extension of it roughly.
Since this function is so flexible, it becomes overwhelming and error-prone. Suppose you want to send a GET request to receive some information from the web.
We all know that it is inappropriate to send data with the GET request because it is not meant to do so in the first place. To reduce the room for errors, the requests module in Python has several high-level functions for sending various types of webrequests that takes into account the needs of a particular request.
Different Web Requests in Different Functions
We can build several functions on top of the request function implemented in the previous section, for different HTTP actions as follows:
(a): The get Function
static CResponse get(const string url, const string headers = "", const int timeout = 5000) { return request("GET", url, "", headers, timeout, false); }
This function sends a GET request to the specified URL. No data is sent to the URL when this function is called as it is meant for receiving the information only.
Usage.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CResponse response = CSession::get("https://httpbin.org/get"); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (string)response.ok); Print("Elapsed Time (ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }
Outputs.
LM 0 09:50:48.904 Requests test (XAUUSD,D1) Status Code: 200 QF 0 09:50:48.904 Requests test (XAUUSD,D1) Reason: OK GQ 0 09:50:48.904 Requests test (XAUUSD,D1) URL: https://httpbin.org/get CD 0 09:50:48.904 Requests test (XAUUSD,D1) OK: true EQ 0 09:50:48.904 Requests test (XAUUSD,D1) Elapsed Time (ms): 1782 KJ 0 09:50:48.904 Requests test (XAUUSD,D1) Headers: JQ 0 09:50:48.904 Requests test (XAUUSD,D1) Date: Fri, 04 Jul 2025 06:50:49 GMT JD 0 09:50:48.904 Requests test (XAUUSD,D1) Content-Type: application/json NL 0 09:50:48.904 Requests test (XAUUSD,D1) Content-Length: 379 NE 0 09:50:48.904 Requests test (XAUUSD,D1) Connection: keep-alive MS 0 09:50:48.904 Requests test (XAUUSD,D1) Server: gunicorn/19.9.0 NH 0 09:50:48.904 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * FL 0 09:50:48.904 Requests test (XAUUSD,D1) Access-Control-Allow-Credentials: true KM 0 09:50:48.904 Requests test (XAUUSD,D1) LL 0 09:50:48.904 Requests test (XAUUSD,D1) Cookies: CE 0 09:50:48.904 Requests test (XAUUSD,D1) Response: { FS 0 09:50:48.904 Requests test (XAUUSD,D1) "args": {}, DJ 0 09:50:48.904 Requests test (XAUUSD,D1) "headers": { PR 0 09:50:48.904 Requests test (XAUUSD,D1) "Accept": "*/*", DF 0 09:50:48.904 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", KO 0 09:50:48.904 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", JL 0 09:50:48.904 Requests test (XAUUSD,D1) "Host": "httpbin.org", QK 0 09:50:48.904 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", JI 0 09:50:48.904 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-686779c9-147d77346060e72a2fa2282f" FH 0 09:50:48.904 Requests test (XAUUSD,D1) }, RI 0 09:50:48.904 Requests test (XAUUSD,D1) "origin": "197.250.227.26", CJ 0 09:50:48.904 Requests test (XAUUSD,D1) "url": "https://httpbin.org/get" PR 0 09:50:48.904 Requests test (XAUUSD,D1) } QG 0 09:50:48.904 Requests test (XAUUSD,D1) KK 0 09:50:48.904 Requests test (XAUUSD,D1) JSON: DQ 0 09:50:48.904 Requests test (XAUUSD,D1) https://httpbin.org/get
While the get function enables you to pass the headers to the HTTP request, you can not control the type of content received from the server even if you set your headers to some Content-type.
(b): The post Function
static CResponse post(const string url, const string data = "", const string headers = "", const int timeout = 5000, const bool is_json=true) { return request("POST", url, data, headers, timeout, is_json); }
This function sends a POST request to the given URL with an optional data payload. Similary to the request function, it automatically sets Content-Type when the argument is_json is set to true.
Usage.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string json = "{\"username\": \"omega\", \"password\": \"secret123\"}"; CResponse response = CSession::post("https://httpbin.org/post",json); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (string)response.ok); Print("Elapsed Time (ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }
Outputs.
NK 0 15:32:13.093 Requests test (XAUUSD,D1) Status Code: 200 OP 0 15:32:13.093 Requests test (XAUUSD,D1) Reason: OK KE 0 15:32:13.093 Requests test (XAUUSD,D1) URL: https://httpbin.org/post GR 0 15:32:13.093 Requests test (XAUUSD,D1) OK: true LD 0 15:32:13.093 Requests test (XAUUSD,D1) Elapsed Time (ms): 1578 GL 0 15:32:13.093 Requests test (XAUUSD,D1) Headers: PK 0 15:32:13.093 Requests test (XAUUSD,D1) Date: Fri, 04 Jul 2025 12:32:13 GMT NR 0 15:32:13.093 Requests test (XAUUSD,D1) Content-Type: application/json GG 0 15:32:13.093 Requests test (XAUUSD,D1) Content-Length: 619 JN 0 15:32:13.093 Requests test (XAUUSD,D1) Connection: keep-alive II 0 15:32:13.093 Requests test (XAUUSD,D1) Server: gunicorn/19.9.0 RF 0 15:32:13.093 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * JR 0 15:32:13.093 Requests test (XAUUSD,D1) Access-Control-Allow-Credentials: true OK 0 15:32:13.093 Requests test (XAUUSD,D1) HQ 0 15:32:13.093 Requests test (XAUUSD,D1) Cookies: GK 0 15:32:13.093 Requests test (XAUUSD,D1) Response: { RN 0 15:32:13.093 Requests test (XAUUSD,D1) "args": {}, EN 0 15:32:13.093 Requests test (XAUUSD,D1) "data": "{\"username\":\"omega\",\"password\":\"secret123\"}", NL 0 15:32:13.093 Requests test (XAUUSD,D1) "files": {}, QJ 0 15:32:13.093 Requests test (XAUUSD,D1) "form": {}, PS 0 15:32:13.093 Requests test (XAUUSD,D1) "headers": { DJ 0 15:32:13.093 Requests test (XAUUSD,D1) "Accept": "*/*", HO 0 15:32:13.093 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", GG 0 15:32:13.093 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", HJ 0 15:32:13.093 Requests test (XAUUSD,D1) "Content-Length": "43", JM 0 15:32:13.093 Requests test (XAUUSD,D1) "Content-Type": "application/json", FK 0 15:32:13.093 Requests test (XAUUSD,D1) "Host": "httpbin.org", MN 0 15:32:13.093 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", IM 0 15:32:13.093 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-6867c9cd-64a83cd77115a2575214fecd" JS 0 15:32:13.093 Requests test (XAUUSD,D1) }, HJ 0 15:32:13.093 Requests test (XAUUSD,D1) "json": { DL 0 15:32:13.093 Requests test (XAUUSD,D1) "password": "secret123", LK 0 15:32:13.093 Requests test (XAUUSD,D1) "username": "omega" RS 0 15:32:13.093 Requests test (XAUUSD,D1) }, FG 0 15:32:13.093 Requests test (XAUUSD,D1) "origin": "197.250.227.26", EO 0 15:32:13.093 Requests test (XAUUSD,D1) "url": "https://httpbin.org/post" FE 0 15:32:13.093 Requests test (XAUUSD,D1) } OL 0 15:32:13.093 Requests test (XAUUSD,D1) MD 0 15:32:13.093 Requests test (XAUUSD,D1) JSON: DD 0 15:32:13.093 Requests test (XAUUSD,D1) https://httpbin.org/post
(c): The put Function
This function sends a PUT request to update a resource at the URL with the given data.
static CResponse put(const string url, const string data = "", const string headers = "", const int timeout = 5000, const bool is_json=true) { return request("PUT", url, data, headers, timeout, is_json); }
Example usage.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string json = "{\"username\": \"omega\", \"password\": \"secret123\"}"; CResponse response = CSession::put("https://httpbin.org/put",json); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (string)response.ok); Print("Elapsed Time (ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }
Outputs.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string json = "{\"update\": true}"; CResponse response = CSession::put("https://httpbin.org/put", json, "", 5000, true); Print("Reason: ", response.reason); Print("Response: ",response.text); }
Outputs.
MG 0 15:51:02.874 Requests test (XAUUSD,D1) Reason: OK EQ 0 15:51:02.874 Requests test (XAUUSD,D1) Response: { HG 0 15:51:02.874 Requests test (XAUUSD,D1) "args": {}, JM 0 15:51:02.874 Requests test (XAUUSD,D1) "data": "{\"update\":true}", LJ 0 15:51:02.874 Requests test (XAUUSD,D1) "files": {}, CM 0 15:51:02.874 Requests test (XAUUSD,D1) "form": {}, FE 0 15:51:02.874 Requests test (XAUUSD,D1) "headers": { RO 0 15:51:02.874 Requests test (XAUUSD,D1) "Accept": "*/*", JI 0 15:51:02.874 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", QL 0 15:51:02.874 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", KE 0 15:51:02.874 Requests test (XAUUSD,D1) "Content-Length": "15", LG 0 15:51:02.874 Requests test (XAUUSD,D1) "Content-Type": "application/json", PL 0 15:51:02.874 Requests test (XAUUSD,D1) "Host": "httpbin.org", KD 0 15:51:02.874 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", KH 0 15:51:02.874 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-6867ce36-76b6e4053cd661ec5e4faa1b" HI 0 15:51:02.874 Requests test (XAUUSD,D1) }, NG 0 15:51:02.874 Requests test (XAUUSD,D1) "json": { DQ 0 15:51:02.874 Requests test (XAUUSD,D1) "update": true PG 0 15:51:02.874 Requests test (XAUUSD,D1) }, PS 0 15:51:02.874 Requests test (XAUUSD,D1) "origin": "197.250.227.26", HD 0 15:51:02.874 Requests test (XAUUSD,D1) "url": "https://httpbin.org/put" RH 0 15:51:02.874 Requests test (XAUUSD,D1) } CI 0 15:51:02.874 Requests test (XAUUSD,D1)
(d): The patch Function
static CResponse patch(const string url, const string data = "", const string headers = "", const int timeout = 5000, const bool is_json=true) { return request("PATCH", url, data, headers, timeout, is_json); }
This function sends a PATCH request to partially update a resource at the URL with the provided data.
Example usage.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string json = "{\"patched\": 1}"; CResponse response = CSession::patch("https://httpbin.org/patch", json, "", 5000, true); Print("Reason: ", response.reason); Print("Response: ",response.text); }
Outputs.
GR 0 16:33:45.258 Requests test (XAUUSD,D1) Reason: OK OF 0 16:33:45.258 Requests test (XAUUSD,D1) Response: { RR 0 16:33:45.258 Requests test (XAUUSD,D1) "args": {}, GJ 0 16:33:45.258 Requests test (XAUUSD,D1) "data": "{\"patched\":1}", NL 0 16:33:45.258 Requests test (XAUUSD,D1) "files": {}, IK 0 16:33:45.258 Requests test (XAUUSD,D1) "form": {}, HS 0 16:33:45.258 Requests test (XAUUSD,D1) "headers": { LE 0 16:33:45.258 Requests test (XAUUSD,D1) "Accept": "*/*", HO 0 16:33:45.258 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", OF 0 16:33:45.258 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", CJ 0 16:33:45.258 Requests test (XAUUSD,D1) "Content-Length": "13", RM 0 16:33:45.258 Requests test (XAUUSD,D1) "Content-Type": "application/json", FK 0 16:33:45.258 Requests test (XAUUSD,D1) "Host": "httpbin.org", MN 0 16:33:45.258 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", GN 0 16:33:45.258 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-6867d837-47b9eb772b7ec6300016aa79" JS 0 16:33:45.258 Requests test (XAUUSD,D1) }, HJ 0 16:33:45.258 Requests test (XAUUSD,D1) "json": { MR 0 16:33:45.258 Requests test (XAUUSD,D1) "patched": 1 FH 0 16:33:45.258 Requests test (XAUUSD,D1) }, RI 0 16:33:45.258 Requests test (XAUUSD,D1) "origin": "197.250.227.26", KK 0 16:33:45.258 Requests test (XAUUSD,D1) "url": "https://httpbin.org/patch" DS 0 16:33:45.258 Requests test (XAUUSD,D1) }
(e): The delete Function
static CResponse delete_(const string url, const string headers = "", const int timeout = 5000, const bool is_json=true) { return request("DELETE", url, "", headers, timeout, is_json); }
This function sends a DELETE request to remove a resource at the given URL. No data payload is used.
Example usage.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CResponse response = CSession::delete_("https://httpbin.org/delete", "", 5000, true); Print("Reason: ", response.reason); Print("Response: ",response.text); }
Outputs.
ML 0 16:43:03.046 Requests test (XAUUSD,D1) Reason: OK EL 0 16:43:03.046 Requests test (XAUUSD,D1) Response: { HH 0 16:43:03.046 Requests test (XAUUSD,D1) "args": {}, OR 0 16:43:03.046 Requests test (XAUUSD,D1) "data": "", JD 0 16:43:03.046 Requests test (XAUUSD,D1) "files": {}, ER 0 16:43:03.046 Requests test (XAUUSD,D1) "form": {}, DK 0 16:43:03.046 Requests test (XAUUSD,D1) "headers": { PR 0 16:43:03.046 Requests test (XAUUSD,D1) "Accept": "*/*", LG 0 16:43:03.046 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", KO 0 16:43:03.046 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", QQ 0 16:43:03.046 Requests test (XAUUSD,D1) "Content-Length": "0", HE 0 16:43:03.046 Requests test (XAUUSD,D1) "Content-Type": "application/json", LS 0 16:43:03.046 Requests test (XAUUSD,D1) "Host": "httpbin.org", GF 0 16:43:03.046 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", JK 0 16:43:03.046 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-6867da67-1926ebf246353ab24461aed9" LK 0 16:43:03.046 Requests test (XAUUSD,D1) }, FL 0 16:43:03.046 Requests test (XAUUSD,D1) "json": null, PG 0 16:43:03.046 Requests test (XAUUSD,D1) "origin": "197.250.227.26", PO 0 16:43:03.046 Requests test (XAUUSD,D1) "url": "https://httpbin.org/delete" HE 0 16:43:03.046 Requests test (XAUUSD,D1) } QL 0 16:43:03.046 Requests test (XAUUSD,D1)
Uploading Files to the Web
The requests module in Python makes it effortless to share and receive files from the internet, unlike the built-in function for Web request in MQL5.
Being able to send files to the web is a necessity for effective communications in today's world. We often want to send chart screenshots to show our trading progress and sometimes demonstrate trading setups and some sort of visual signals directly from the MetaTrader 5 chart.
This is the trickiest part in our CSession-MQL5 class because we have to be mindful of the file types users can upload to the web and apply the right encoding to the binary information extracted directly from the files. Not to mention, we need the right HTTP header(s) for each type of file.
CResponse CSession::request(const string method, const string url, const string data, const string &files[], const string headers = "", const int timeout = 5000, const bool is_json=true) { char result[]; string result_headers; string temp_headers = m_headers; string boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; //for setting boundaries between data types and files in the form data CArrayChar final_body; //Final body uchar array CResponse response; //a structure containing various response fields // Append user headers if (headers != "") temp_headers += headers; bool use_multipart = ArraySize(files) > 0; //Check if files are attached //--- Create a multi part request if (use_multipart) // If multipart, assemble full body (JSON + files) { temp_headers = UpdateHeader(temp_headers, "Content-Type", "multipart/form-data; boundary=" + boundary + "\r\n"); //Update the headers //--- JSON part (or form data) if (StringLen(data) > 0) { string json_data = ""; if (is_json) //if Json data is given alongside the files { CJAVal js(NULL, jtUNDEF); if (js.Deserialize(data, CP_UTF8)) js.Serialize(json_data); //Serialize the JSON data } string json_part = "--" + boundary + "\r\n"; json_part += "Content-Disposition: form-data; name=\"metadata\"\r\n"; json_part += "Content-Type: application/json\r\n\r\n"; json_part += json_data + "\r\n"; char json_bytes[]; StringToCharArray(json_part, json_bytes, 0, StringLen(json_part), CP_UTF8); final_body.AddArray(json_bytes); } //--- File parts for (uint i = 0; i < files.Size(); i++) { string filename = GetFileName(files[i]); char file_data[]; //for storing the file data in binary format int file_handle = FileOpen(filename, FILE_BIN | FILE_SHARE_READ); // Read the file in binary format if (file_handle == INVALID_HANDLE) { printf("func=%s line=%d, Failed to read the file '%s'. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); continue; //skip to the next file if the current file is invalid } int fsize = (int)FileSize(file_handle); ArrayResize(file_data, fsize); if (FileReadArray(file_handle, file_data, 0, fsize)==0) { printf("func=%s line=%d, No data found in the file '%s'. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); FileClose(file_handle); continue; //skip to the next file if the current file is invalid } FileClose(file_handle); //close the current file //--- Append files header and content type as detected to the request string file_part = "--" + boundary + "\r\n"; file_part += StringFormat("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n", filename); file_part += StringFormat("Content-Type: %s\r\n\r\n", GuessContentType(filename)); char file_header[]; StringToCharArray(file_part, file_header, 0, StringLen(file_part), CP_UTF8); //UTF-8 Encoding is a must final_body.AddArray(file_header); //Add the file header final_body.AddArray(file_data); //Add the file in binary format, the actual file //--- append the new line — critical for HTTP form parsing. final_body.Add('\r'); final_body.Add('\n'); } //--- Final boundary string closing = "--" + boundary + "--\r\n"; char closing_part[]; StringToCharArray(closing, closing_part); final_body.AddArray(closing_part); } else // no files attached { //--- If it's just JSON or plain form data string body_data = data; if (is_json) { CJAVal js(NULL, jtUNDEF); if (js.Deserialize(data, CP_UTF8)) js.Serialize(body_data); temp_headers = UpdateHeader(temp_headers, "Content-Type", "application/json"); } else temp_headers = UpdateHeader(temp_headers, headers); //--- char array[]; StringToCharArray(body_data, array, 0, StringLen(body_data), CP_UTF8); //Use UTF-8 similar requests in Python, This is very crucial final_body.AddArray(array); } char final_body_char_arr[]; CArray2Array(final_body, final_body_char_arr); if (MQLInfoInteger(MQL_DEBUG)) Print("Final body:\n",CharArrayToString(final_body_char_arr, 0 , final_body.Total(), CP_UTF8)); //--- Send the request uint start = GetTickCount(); //starting time of the request int status = WebRequest(method, url, temp_headers, timeout, final_body_char_arr, result, result_headers); //trigger a webrequest function if(status == -1) { PrintFormat("WebRequest failed with error %s", ErrorDescription(GetLastError())); response.status_code = 0; return response; } //--- Fill the response struct response.elapsed = GetTickCount() - start; response.text = CharArrayToString(result); response.status_code = status; response.headers = result_headers; response.url = url; response.ok = (status >= 200 && status < 400); response.reason = WebStatusText(status); ArrayCopy(response.content, result); //--- CJAVal js; if (js.Deserialize(response.text)) response.json = js; return response; }
Similarly to the previous request function for performing any request to the web, this one does a similar task but, it is capable of detecting the files given by the user in the function argument and embed them in the HTTP request.
When the files array is empty, i.e,. The user gives no files. The above function performs a regular HTTP request as discussed prior, but when the files are received, it updates the HTTP header into a multipart/form-data content type that enables the HTTP request to distinguish between different information and data types given.
temp_headers = UpdateHeader(temp_headers, "Content-Type", "multipart/form-data; boundary=" + boundary + "\r\n"); //Update the headers
The final_body array is responsible for gluing all the data together (content and files) into a single character (char) array variable, similarly to what a form does on the web. This is done inside a loop that iterates throught the files array which carries all the files you'd like to send to the server at once.
//--- File parts for (uint i = 0; i < files.Size(); i++) { string filename = GetFileName(files[i]); char file_data[]; //for storing the file data in binary format int file_handle = FileOpen(filename, FILE_BIN | FILE_SHARE_READ); // Read the file in binary format if (file_handle == INVALID_HANDLE) { printf("func=%s line=%d, Failed to read the file '%s'. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); continue; //skip to the next file if the current file is invalid } int fsize = (int)FileSize(file_handle); ArrayResize(file_data, fsize); if (FileReadArray(file_handle, file_data, 0, fsize)==0) { printf("func=%s line=%d, No data found in the file '%s'. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); FileClose(file_handle); continue; //skip to the next file if the current file is invalid } FileClose(file_handle); //close the current file //--- Append files header and content type as detected to the request string file_part = "--" + boundary + "\r\n"; file_part += StringFormat("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n", filename); file_part += StringFormat("Content-Type: %s\r\n\r\n", GuessContentType(filename)); char file_header[]; StringToCharArray(file_part, file_header, 0, StringLen(file_part), CP_UTF8); //UTF-8 Encoding is a must final_body.AddArray(file_header); //Add the file header final_body.AddArray(file_data); //Add the file in binary format, the actual file //--- append the new line — critical for HTTP form parsing. final_body.Add('\r'); final_body.Add('\n'); }
To allow different types of files (videos, images, micosoft documents, etc) to be sent to the server using this one function.
The function GuessContentType detects the type of file given based on the file's extension and returns the right Content-type to be added to the HTTP multipart-form header.
string CSession::GuessContentType(string filename) { StringToLower(filename); // Normalize for case-insensitivity if(StringFind(filename, ".txt") >= 0) return "text/plain"; if(StringFind(filename, ".json") >= 0) return "application/json"; if(StringFind(filename, ".xml") >= 0) return "application/xml"; //... other files //--- Images if(StringFind(filename, ".png") >= 0) return "image/png"; if(StringFind(filename, ".jpg") >= 0 || StringFind(filename, ".jpeg") >= 0) return "image/jpeg"; if(StringFind(filename, ".gif") >= 0) return "image/gif"; //...etc //--- Audio if(StringFind(filename, ".mp3") >= 0) return "audio/mpeg"; if(StringFind(filename, ".wav") >= 0) return "audio/wav"; if(StringFind(filename, ".ogg") >= 0) return "audio/ogg"; //--- Video if(StringFind(filename, ".mp4") >= 0) return "video/mp4"; if(StringFind(filename, ".avi") >= 0) return "video/x-msvideo"; if(StringFind(filename, ".mov") >= 0) return "video/quicktime"; if(StringFind(filename, ".webm") >= 0) return "video/webm"; if(StringFind(filename, ".mkv") >= 0) return "video/x-matroska"; //--- Applications if(StringFind(filename, ".pdf") >= 0) return "application/pdf"; if(StringFind(filename, ".zip") >= 0) return "application/zip"; //... etc //--- Microsoft Office if(StringFind(filename, ".doc") >= 0) return "application/msword"; if(StringFind(filename, ".docx") >= 0) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; if(StringFind(filename, ".xls") >= 0) return "application/vnd.ms-excel"; //...etc return "application/octet-stream"; // Default fallback }
Example usage.
Suppose we have an image — a screenshot taken from the MetaTrader 5 chart, that we want to send to a server.
To work with these files easily, you have to ensure they are under the MQL5 DataPath.
Using tempfiles.org server as our API endpoint.
Again, for this to work, make sure you add the URL tempfiles.org to the list of allowed URLs in MetaTrader 5;
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string files[] = {"chart.jpg"}; CResponse response = CSession::request("POST","https://tmpfiles.org/api/v1/upload","",files); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (string)response.ok); Print("Elapsed Time (ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }
Outputs.
CF 0 17:06:25.063 Requests test (XAUUSD,D1) Status Code: 200 RL 0 17:06:25.063 Requests test (XAUUSD,D1) Reason: OK QM 0 17:06:25.063 Requests test (XAUUSD,D1) URL: https://tmpfiles.org/api/v1/upload FN 0 17:06:25.063 Requests test (XAUUSD,D1) OK: true OH 0 17:06:25.063 Requests test (XAUUSD,D1) Elapsed Time (ms): 1594 FQ 0 17:06:25.063 Requests test (XAUUSD,D1) Headers: RN 0 17:06:25.063 Requests test (XAUUSD,D1) Server: nginx/1.22.1 QO 0 17:06:25.063 Requests test (XAUUSD,D1) Content-Type: application/json KJ 0 17:06:25.063 Requests test (XAUUSD,D1) Transfer-Encoding: chunked CS 0 17:06:25.063 Requests test (XAUUSD,D1) Connection: keep-alive KE 0 17:06:25.063 Requests test (XAUUSD,D1) Cache-Control: no-cache, private RM 0 17:06:25.063 Requests test (XAUUSD,D1) Date: Thu, 10 Jul 2025 14:06:25 GMT DF 0 17:06:25.063 Requests test (XAUUSD,D1) X-RateLimit-Limit: 60 GN 0 17:06:25.063 Requests test (XAUUSD,D1) X-RateLimit-Remaining: 59 CN 0 17:06:25.063 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * NF 0 17:06:25.063 Requests test (XAUUSD,D1) ED 0 17:06:25.063 Requests test (XAUUSD,D1) Cookies: RM 0 17:06:25.063 Requests test (XAUUSD,D1) Response: {"status":"success","data":{"url":"http://tmpfiles.org/5459540/chart.png"}} LS 0 17:06:25.063 Requests test (XAUUSD,D1) JSON: HJ 0 17:06:25.063 Requests test (XAUUSD,D1)
Upon a successful POST request, tempfiles.org returns a JSON response containing a URL endpoint of where the file is hosted. We can go to this link and observe the image file in the web browser.
Receiving and Downloading Files from the Web
Again, the internet is meant for sharing various information and files, being able to receive different files in MQL5 is handy as it helps in receiving data in CSV, and Excel formats; receiving trained machine learning models and their parameters in different binary formats, and much more.
The implemented get function is already capable of doing this when given the right API endpoint.
For example; Let's get an image from httpbin.org and save it in the MQL5 datapath.
#include <requests.mqh> CSession requests; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Get the image file from the web CResponse response = requests.get("https://httpbin.org/image/jpeg"); }
When this function is executed successfully, it returns the image file/data encrypted (in binary format).
This file is located in the variable CResponse::content.
void OnStart() { //--- Get the image file from the web CResponse response = requests.get("https://httpbin.org/image/jpeg"); //--- Saving an image received in binary format stored in response.content int handle = FileOpen("image.jpg", FILE_WRITE|FILE_BIN|FILE_SHARE_WRITE); //Open a .jpg file for writting an image to it if (handle == INVALID_HANDLE) //Check the handle { printf("Failed to open an Image. Error=%s",ErrorDescription(GetLastError())); return; } if (FileWriteArray(handle, response.content)==0) //write all binary data to a image.jpg file { printf("Failed to write an Image. Error=%s",ErrorDescription(GetLastError())); return; } FileClose(handle); }
Outputs.
An image containing a fox (or whatever animal that is) is stored under the MQL5/Files folder.
Session and Cookies Handling
You may have noticed that all the functions in the CSession class are static, making this class a "static one".
class CSession { protected: //.... other lines of code public: CSession(const string headers, const string cookies=""); // Provides headers cookies persistance ~CSession(void); static void SetCookie(const string cookie) { if (StringLen(m_cookies) > 0) m_cookies += "; "; m_cookies += cookie; } static void ClearCookies() { m_cookies = ""; } static void SetBasicAuth(const string username, const string password); //--- static CResponse request(const string method, const string url, const string data, const string &files[], const string headers = "", const int timeout = 5000, const bool is_json=true); // High-level request helpers static CResponse get(const string url, const string headers = "", const int timeout = 5000) { string files[]; return request("GET", url, "", files, headers, timeout, false); } //... other functions }
This is aimed to give an option for developers to either use the requests library partially or as a whole to mimick how the requests module in Python operates.
(a): Using the entire class object
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string headers = "Content-Type: application/json;"; string cookies = "sessionid=abc123"; CSession session(headers, cookies); //New session, class constructor calle with CResponse response = session.get("https://httpbin.org/cookies"); //receive cookies from the server Print("HTTP response"); Print("--> Status Code: ", response.status_code); Print("--> Reason: ", response.reason); Print("--> URL: ", response.url); Print("--> OK: ", (string)response.ok); Print("--> Elapsed Time (ms): ", response.elapsed); Print("--> Headers:\n", response.headers); Print("--> Cookies: ", response.cookies); Print("--> Response text: ",response.text); Print("--> JSON:\n", response.json.ToStr()); }
Outputs.
DS 0 11:38:36.272 Requests test (XAUUSD,D1) HTTP response RI 0 11:38:36.272 Requests test (XAUUSD,D1) --> Status Code: 200 KR 0 11:38:36.272 Requests test (XAUUSD,D1) --> Reason: OK NG 0 11:38:36.272 Requests test (XAUUSD,D1) --> URL: https://httpbin.org/cookies IF 0 11:38:36.272 Requests test (XAUUSD,D1) --> OK: true QQ 0 11:38:36.272 Requests test (XAUUSD,D1) --> Elapsed Time (ms): 2141 IH 0 11:38:36.272 Requests test (XAUUSD,D1) --> Headers: FQ 0 11:38:36.272 Requests test (XAUUSD,D1) Date: Sat, 12 Jul 2025 08:38:36 GMT RE 0 11:38:36.272 Requests test (XAUUSD,D1) Content-Type: application/json FO 0 11:38:36.272 Requests test (XAUUSD,D1) Content-Length: 49 DE 0 11:38:36.272 Requests test (XAUUSD,D1) Connection: keep-alive CR 0 11:38:36.272 Requests test (XAUUSD,D1) Server: gunicorn/19.9.0 PK 0 11:38:36.272 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * HM 0 11:38:36.272 Requests test (XAUUSD,D1) Access-Control-Allow-Credentials: true QN 0 11:38:36.272 Requests test (XAUUSD,D1) DL 0 11:38:36.272 Requests test (XAUUSD,D1) --> Cookies: LG 0 11:38:36.272 Requests test (XAUUSD,D1) --> Response text: { EP 0 11:38:36.272 Requests test (XAUUSD,D1) "cookies": { HJ 0 11:38:36.272 Requests test (XAUUSD,D1) "sessionid": "abc123" RN 0 11:38:36.272 Requests test (XAUUSD,D1) } DE 0 11:38:36.272 Requests test (XAUUSD,D1) } EM 0 11:38:36.272 Requests test (XAUUSD,D1) MD 0 11:38:36.272 Requests test (XAUUSD,D1) --> JSON: GH 0 11:38:36.272 Requests test (XAUUSD,D1)
Using the entire class by calling the class constructor and passing the header and cookies (optional), enables you to work with global header values and use the same cookies across all the HTTP request that will be made using the same class instance, this is what we call a HTTP session.
(b): Using functions from the class separately
For making simple HTTP requests without being in an HTTP session, i.e., for managing HTTP new headers and cookies every time.
Below is how you use the functions from the CSession class directly without instantiating the class object.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CResponse response = CSession::get("https://httpbin.org/get"); //The get request Print("HTTP response"); Print("--> Status Code: ", response.status_code); Print("--> Reason: ", response.reason); Print("--> URL: ", response.url); Print("--> OK: ", (string)response.ok); Print("--> Elapsed Time (ms): ", response.elapsed); Print("--> Headers:\n", response.headers); Print("--> Cookies: ", response.cookies); Print("--> Response text: ",response.text); Print("--> JSON:\n", response.json.ToStr()); }
Basic Authentication
All the functions in the requests module offered in Python have an option to send details for a basic (simple) authentication to the server.
import requests response = requests.get("https://httpbin.org/headers", auth=("user", "pass")) print(response.text)
Below is a similar functionality in our MQL5 class.
void CSession::SetBasicAuth(const string username, const string password) { string credentials = username + ":" + password; string encoded = Base64Encode(credentials); //Encode the credentials m_headers = UpdateHeader(m_headers, "Authorization", "Basic " + encoded); //Update HTTP headers with the authentication information }
Unlike in the Python module, where developers can send these authentication parameters directly in a function, our MQL5 class does it slightly differently by allowing users to send these basic authentication parameters in a separate function.
The SetBasicAuth function updates the headers in the class by adding the authorization credentials; these values will be available to all the functions called afterward using the same class instance.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CSession::SetBasicAuth("user", "pass"); //Sets authentication parameters to the HTTP header CResponse response = CSession::get("https://httpbin.org/headers"); //Get the headers from the server Print("HTTP response"); Print("--> Headers:\n", response.headers); Print("--> Response text: ",response.text); }
Outputs.
IE 0 14:23:23.885 Requests test (XAUUSD,D1) HTTP response FQ 0 14:23:23.885 Requests test (XAUUSD,D1) --> Headers: KI 0 14:23:23.885 Requests test (XAUUSD,D1) Date: Sat, 12 Jul 2025 11:23:23 GMT MM 0 14:23:23.885 Requests test (XAUUSD,D1) Content-Type: application/json HK 0 14:23:23.885 Requests test (XAUUSD,D1) Content-Length: 385 IR 0 14:23:23.885 Requests test (XAUUSD,D1) Connection: keep-alive JJ 0 14:23:23.885 Requests test (XAUUSD,D1) Server: gunicorn/19.9.0 IS 0 14:23:23.885 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * QE 0 14:23:23.885 Requests test (XAUUSD,D1) Access-Control-Allow-Credentials: true HF 0 14:23:23.885 Requests test (XAUUSD,D1) KG 0 14:23:23.885 Requests test (XAUUSD,D1) --> Response text: { KP 0 14:23:23.885 Requests test (XAUUSD,D1) "headers": { GH 0 14:23:23.885 Requests test (XAUUSD,D1) "Accept": "*/*", CM 0 14:23:23.885 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", DQ 0 14:23:23.885 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", NE 0 14:23:23.885 Requests test (XAUUSD,D1) "Authorization": "Basic dXNlcjpwYXNz", NS 0 14:23:23.885 Requests test (XAUUSD,D1) "Cookie": "session=abc123;max-age=60;", KL 0 14:23:23.885 Requests test (XAUUSD,D1) "Host": "httpbin.org", HK 0 14:23:23.885 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", NI 0 14:23:23.885 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-687245ab-2065056c28f0024b71a6446f" GH 0 14:23:23.885 Requests test (XAUUSD,D1) } IF 0 14:23:23.885 Requests test (XAUUSD,D1) }
Dealing with URL Parameters
Another cool thing that the requests module in Python does is that it manages the URL received and its parameters before sending the final web request.
import requests response = requests.get("https://httpbin.org/get", params={"param1": "value1"}) print(response.url)
Outputs.
https://httpbin.org/get?param1=value1
We take a slightly different approach in our MQL5 class. Instead of passing URL parameters to all the functions, which complicates them, we have a separate utility function that helps in creating the final URL with parameters given the original one and its associated parameters.
#include <requests.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string keys[] = {"user", "id", "lang"}; string values[] = {"omega", "123", "mql5 test"}; string parent_url = "https://httpbin.org/get"; string final_url = CSession::BuildUrlWithParams(parent_url, keys, values); //builds a URL with the given parameters Print("final url: ",final_url); }
Outputs.
2025.07.12 15:41:57.924 Requests test (XAUUSD,D1) final url: https://httpbin.org/get?user=omega&id=123&lang=mql5+test
After crafting a URL with its associated parameters, you can then use it in making HTTP web requests.
CResponse response = CSession::get(final_url); //Get the headers from the server Print("HTTP response"); Print("--> Headers:\n", response.headers); Print("--> Response text: ",response.text);
Outputs.
JK 0 15:42:00.398 Requests test (XAUUSD,D1) --> Headers: QP 0 15:42:00.398 Requests test (XAUUSD,D1) Date: Sat, 12 Jul 2025 12:41:59 GMT QK 0 15:42:00.398 Requests test (XAUUSD,D1) Content-Type: application/json PM 0 15:42:00.398 Requests test (XAUUSD,D1) Content-Length: 525 ED 0 15:42:00.398 Requests test (XAUUSD,D1) Connection: keep-alive FP 0 15:42:00.398 Requests test (XAUUSD,D1) Server: gunicorn/19.9.0 MH 0 15:42:00.398 Requests test (XAUUSD,D1) Access-Control-Allow-Origin: * EK 0 15:42:00.398 Requests test (XAUUSD,D1) Access-Control-Allow-Credentials: true LM 0 15:42:00.398 Requests test (XAUUSD,D1) OI 0 15:42:00.398 Requests test (XAUUSD,D1) --> Response text: { RQ 0 15:42:00.398 Requests test (XAUUSD,D1) "args": { CR 0 15:42:00.398 Requests test (XAUUSD,D1) "id": "123", KF 0 15:42:00.398 Requests test (XAUUSD,D1) "lang": "mql5 test", HI 0 15:42:00.398 Requests test (XAUUSD,D1) "user": "omega" KP 0 15:42:00.398 Requests test (XAUUSD,D1) }, MP 0 15:42:00.398 Requests test (XAUUSD,D1) "headers": { IH 0 15:42:00.398 Requests test (XAUUSD,D1) "Accept": "*/*", QL 0 15:42:00.398 Requests test (XAUUSD,D1) "Accept-Encoding": "gzip, deflate", JQ 0 15:42:00.398 Requests test (XAUUSD,D1) "Accept-Language": "en;q=0.5", NF 0 15:42:00.398 Requests test (XAUUSD,D1) "Cookie": "session=abc123;max-age=60;", KQ 0 15:42:00.398 Requests test (XAUUSD,D1) "Host": "httpbin.org", HP 0 15:42:00.398 Requests test (XAUUSD,D1) "User-Agent": "MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)", ND 0 15:42:00.398 Requests test (XAUUSD,D1) "X-Amzn-Trace-Id": "Root=1-68725817-67dc04cf43ac75b012094481" KE 0 15:42:00.398 Requests test (XAUUSD,D1) }, CQ 0 15:42:00.398 Requests test (XAUUSD,D1) "origin": "197.250.227.235", MD 0 15:42:00.398 Requests test (XAUUSD,D1) "url": "https://httpbin.org/get?user=omega&id=123&lang=mql5+test" IK 0 15:42:00.398 Requests test (XAUUSD,D1) } LN 0 15:42:00.398 Requests test (XAUUSD,D1)
Awesome! The server has even responded with the args key in our response JSON text, which indicates the process of building a URL with its associated parameters was a success.
The Bottom Line
With all the free, open-source knowledge and information available to the public. Coding should not be difficult in today's world.
After learning from how the requests module operates in Python, I was able to implement a similar module in MQL5 to aid us in making HTTP requests to external servers from MetaTrader 5.
However, while the syntax and function calls might look similar to the ones offered in the requests module offered in Python, this MQL5 module is far from complete; we need to test it vigorously and keep improving it together that's why I created a Repository on Forge MQL5 linked -> https://forge.mql5.io/omegajoctan/Requests .
So, don't hesitate to update the code from there and let us know your thoughts in the discussion section.
Peace out.
Attachments Table
Filename | Descrioption & Usage |
---|---|
Include\errordescription.mqh | Contains descriptions of all error codes produced by MQL5 and MetaTrader 5 |
Include\Jason.mqh | The library for serializing and deserializing of strings in a JSON-like format. |
Include\requests.mqh | The main module resembling the requests module from Python. |
Scripts\Requests test.mq5 | The main script for testing all functions and methods described in this post. |
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use