I am trying to use boost::asio to make a synchronous http POST request to localhost:55001/predict with a large body (around 8784000 characters). I am able to do this just fine in python using the requests package. My server is able to handle the large body just fine so I know the issue is probably not on the server side.
The Problem:
I set up my request based on other stackoverflow posts for boost::asio POST requests. Here is the main chunk of code for how I set up my POST request. EDIT: I need to use Boost because my client cannot use C++11.
The features variable contains a very long string (360 JSONs represented as string delimited by &).
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "POST " << /predict << " HTTP/1.1\r\n";
request_stream << "Host: " << localhost:55001 << "\r\n";
request_stream << "Content-Type: text/plain; charset=utf-8 \r\n";
request_stream << "Content-Length: " << features.length() << "\r\n";
request_stream << "Connection: close\r\n\r\n";
request_stream << features;
// Send the request
boost::asio::write(socket, request);
// Get the response
boost::asio::streambuf response;
boost::asio::read_until(socket, response, "\r\n");
I get the following error/response from the server:
Response returned with status code 413
libc++abi.dylib: terminating with uncaught exception of type boost::exception_detail::clone_impl >: (1): expected value
The Jetty server I'm sending the request to complains of a HttpParser:HttpParser Full which implies that the request is to large. Considering I was able to send the full request using Python's request package I know the the Jetty server is definitely able to handle requests of this size. However, this means I am packaging my request incorrectly with boost::asio.
When features just contained 2 JSONs (still delimited by &) everything works fine and the response contains the results I expect.
I suspect that this issue is because I am writing too much data to the buffer and that I need to send several buffers in a single request. Furthermore, I imagine that Python's request package is handling these issues internally which is why the python code works just fine. Here is the line I use to send the request in python.
response = requests.post('http://localhost:55001/predict', data=features.encode('utf-8'))
1) Can someone explain how to send a lot of data in the request body of a synchronous POST request using boost::asio? I am unfamiliar with C++ so an example would be helpful.
2) Is Python's Request package abstracting these issues away from me?
Let me know if there is additional information I can provide to help you answer this. Thanks in advance!
Boost is great but if all you're trying to do is HTTP GET and POST methods (and you are using C++11) then consider something like cpr C++ Requests. It's a lightweight library that wraps libcurl and is modeled after the Python Requests project.
From the documentation, here is a quick example of making a POST request
#include <cpr/cpr.h>
auto r = cpr::Post(cpr::Url{"http://www.httpbin.org/post"},
cpr::Body{"This is raw POST data"},
cpr::Header{{"Content-Type", "text/plain"}});
std::cout << r.text << std::endl;
/*
* {
* "args": {},
* "data": "This is raw POST data",
* "files": {},
* "form": {},
* "headers": {
* ..
* "Content-Type": "text/plain",
* ..
* },
* "json": null,
* "url": "http://www.httpbin.org/post"
* }
*/
Related
I am working on a C++ project where i listen on sockets and generate HTTP responses based on the requests i get from my clients on my fds, in short i use my browser to send a request, i end up getting the raw request, i parse it and generate the corresponding http response.
However in the case of large POST requests, usually what happens is that i get partial requests, so in the first part i will usually only find the first line (version/method/uri), some headers but no body, and i guess am supposed to get the rest of the body somehow, however i am unable to figure out two things,
first of all how do i know if the request i am getting is partial or completed from just the first part ? i am not getting any information relating to range, here's the first part i get when my client sends me a POST request.
POST / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Content-Length: 8535833
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:8081
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryOs6fsdbaegBIumqh
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8081/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr,en-US;q=0.9,en;q=0.8
how can i figure out just from this whether or not am getting a partial request or just a faulty request (I need to generate a 400 error in the case of a request that says it has X content-length but the body size is different)
second question is, suppose i already know whether or not its partial, how do i proceed with storing the entire request in a buffer before sending it to my parser and generating a response ? here's my reception function (i already know the client's fd, so i just recv on it
void Client::receive_request(void)
{
char buffer[2024];
int ret;
ret = recv(_fd, buffer, 2024, 0);
buffer[ret] = 0;
_received_request += buffer;
_bytes_request += ret;
std::cout << "Raw Request:\n" << _received_request << std::endl;
if (buffer[ret-1] == '\n')
{
_ready_request = true;
_request.parse(_received_request, _server->get_config());
}
}
and here's the code that checks whether or not a client is attempting to send a request, parse and generate a response
int Connections::check_clients() {
int fd;
for (std::vector<Client*>::iterator client = clients.begin();
client != clients.end() && ready_fd != 0 ; client++)
{
fd = (*client)->get_fd();
if (FD_ISSET(fd, &ready_rset))
{
ready_fd--;
(*client)->receive_request();
if ((*client)->request_is_ready())
{
(*client)->wait_response();
close(fd);
FD_CLR(fd, &active_set);
fd_list.remove(fd);
max_fd = *std::max_element(fd_list.begin(), fd_list.end());
free(*client);
client = clients.erase(client);
}
}
}
return 0;
}
as you can see am coding everything in C++ (98) and would rather not get answers that just dismiss my questions and refer me to different technologies or libraries, unless it will help me understand what am doing wrong and how to handle partial requests.
for info, am only handling HTTP 1.1(GET/POST/DELETE only) and i usually only get this issue when am getting a large chunked file or a file upload that has a very large body. thank you
PS : if needed i can link up the github repo of the current project if you wanna look further into the code
how can i figure out just from this whether or not am getting a partial request or just a faulty request (I need to generate a 400 error in the case of a request that says it has X content-length but the body size is different)
The body size is, by definition, the size of the Content-Length field. Any bytes that you receive afterwards belong to the next HTTP request (see HTTP pipelining). If you do not receive Content-Length bytes within a reasonable time period, then you can make the server issue a 408 Request Timeout error.
second question is, suppose i already know whether or not its partial, how do i proceed with storing the entire request in a buffer before sending it to my parser and generating a response ? here's my reception function (i already know the client's fd, so i just recv on it
Your posted code has at least the following problems:
You should check the return value of recv to determine whether the function succeeded or failed, and if it failed, you should handle the error appropriately. In your current code, if recv fails with the return value -1, then you will write to the array buffer out of bounds, causing undefined behavior.
It does not seem appropriate to use the line if (buffer[ret-1] == '\n'). The HTTP request header will be over when you encounter a "\r\n\r\n", and the HTTP request body will be over when you have read Content-Length bytes of the body. The ends of the header and body will not necessarily occur at the end of the data read by recv, but can also occur in the middle. If you want to support HTTP pipelining, then the additional data should be handled by the handler for the next HTTP request. If you don't want to support HTTP pipelining, then you can simply discard the additional data and use Connection: close in the HTTP response header.
You seem to be using a null terminating character to mark the end of the data read by recv. However, this will not work if a byte with the value 0 is part of the HTTP request. It is probably safe to assume that such a byte should not be part of the HTTP request header, but it is probably not safe to assume that such a byte won't be part of the HTTP request body (for example when using POST with binary data).
I am trying to build a web server for videos and my client is a web-browser. I am using HTTP with header structure with content-Disposition: attachment
std::string make_header(std::string filename, int file_size)
{
std::ostringstream header;
header << "HTTP/1.1 200 OK\r\n";
header << "Content-Type: " << get_contenttype(filename) << "\r\n";
header << "Content-Disposition: attachment; filename =\"" << filename << "\"\r\n";
header << "Connection: close\r\n";
header << "Content-Length: " << file_size << "\r\n\r\n";
return header.str();
}
I am able to send small files but as soon as large files are given the server takes lot of time. I am using the following method to copy the video file to a local variable.
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
Is there any way to send file using HTTP without actually loading the entire file into memory.
The short answer is: Yes, of course. In the end, HTTP goes over TCP, which goes over IPv4/v6. IP is a sequence of small packets, and TCP is even a bytestream protocol. The other side won't notice that you are still reading bytes from disk when you're sending the first bytes.
In practice, that means the client can't see or won't care how many calls to send() you made. One bug call or a thousand small calls are equivalent.
I wouldn't bother with istreambuf_iterator. I'd just use fread, but I'd still use a std::vector<char> for the buffer. Just read 1 MB chunks and send those. Your OS isn't going to choke on either an 1MB disk read or a 1MB send call.
For a higher-end solution I'd use Boost::asio on Windows, or sendfile on Linux.
EDIT5:
I eventually fixed this issue by more or less throwing away half my code. Rather than sending data to a ruby server using HTTP, I'm now using MQTT to a broker to a NodeJS server. The NodeJS part here isn't important but to anyone else having this issue I STRONGLY recommend sending all IoT data using MQTT, and that's what solved my issue.
I'm currently trying to send data collected from sensors on an Arduino WiFi rev2, to my rails server hosted on Heroku. I do this by sending my data in a JSON format. My problem is that while my methods seem to work initially, with the first few POST requests being received and processed fine, after 2-3 requests the arduino hangs, and I receive status code: -2. I'm using the ArduinoHttpClient library.
I've tried using a local server, which has the same problem, aswell as sending the POST request via both curl and postman. Both curl and postman seem to work as expected, so I imagine the issue is with the arduino code although I can't be sure.
client.beginRequest();
client.post("/input");
client.sendHeader("Content-Type", "application/json");
client.sendHeader("Content-Length", postData.length());
client.beginBody();
client.println(postData);
client.endRequest();
LED(0,128,0);
Serial.println("Gone");
int statusCode = client.responseStatusCode();
String response = client.responseBody();
Serial.print("Status code: ");
Serial.println(statusCode);
Serial.print("Response: ");
Serial.println(response);
When this code fails, the arduino will hang for about 20-40 seconds and I will receive 'status code -3' from the Serial. However I have also received status code -2 and -4 in the past.
When it does succeed I receive the following: "Status code: 204" which is what I would expect.
EDIT:
I've since tried posting to requestcatcher.com, and the problem persisted. I'm therefore fairly confident this is an arduino problem, I also received the following output:
POST /input HTTP/1.1
Host: arduino.requestcatcher.com
Connection: close
Connection: close
Content-Length: 88
Content-Type: application/json
User-Agent: Arduino/2.2.0
{"inputs":[{"input_id":"1","value":1.778002}{"input_id":"2","value":18.037}],"id":"13"}
EDIT 2:
I accidentally discovered that the POST requests go through fine if the "Content-Length:" Header is omitted. Obviously no JSON actually gets sent so this does not fix my issue but it is likely that this header or the JSON itself is the issue.
EDIT 3:
Regardless of server I receive either status code -4, or -3, even on request catcher.
EDIT 4:
After various adjustments, code now looks as below. This seems to have helped a little and it fails less often but still does fail. I'm beginning to wonder if this is a problem with ArduinoHttpClient.
String postData = "";
serializeJson(doc, postData);
serializeJson(doc, Serial);
Serial.println(postData)
client.post("/input", "application/json", postData.c_str());
LED(0,128,0);
Serial.println("Gone");
int statusCode = client.responseStatusCode();
Serial.print("Status code: ");
Serial.println(statusCode);
client.stop();
doc.clear();
lastCycle = millis();
Try replacing
client.beginRequest();
client.post("/input");
client.sendHeader("Content-Type", "application/json");
client.sendHeader("Content-Length", postData.length());
client.beginBody();
client.println(postData);
client.endRequest();
with just
String contentType = "application/json";
client.post("/input", contentType, postData);
or
client.post("/input", "application/json", postData.c_str());
You don't need to explicitly specify the request headers - or call beginRequest(), etc. - when using the post() method(s) in that library.
I'm trying to develop an FTP protocol in c++. It takes the type of the request from the client as it is (get or put) however, the server always receives a put request in both cases and does none of the functionality.
Here the code that compare the type of the request:
if(strcmp(argv[3],"get")==0)
smsg.type=REQ_GET; //REQ_TIME;
else if (strcmp(argv[3],"put")==0)
{
smsg.type=REQ_PUT; //REQ_SIZE;
}
else err_sys("Wrong request type\n");
and this is the part that execute when a get request is sent to the server:
if(smsg.type=REQ_GET)
{
cout<<"Iam inside get"<<endl;
cout<<smsg.type<<endl;
//send out GET message
memcpy(smsg.buffer,&req,sizeof(req)); //copy the request to the msg's buffer
smsg.length=sizeof(req);
fprintf(stdout,"Send a GET request to %s\n",argv[1]);
if (msg_send(sock,&smsg) != sizeof(req))
err_sys("Sending req packet error.,exit");
//receive the response
if(msg_recv(sock,&rmsg)!=rmsg.length)
err_sys("recv response error,exit");
//cast it to the response structure
respp=(Resp *)rmsg.buffer;
printf("Response:%s\n\n\n",respp->response);}
when displaying "respp->response" it doesn't return anything, and always the size of the file is zero.
Does anyone have an idea about how to solve this problem?
Any help will be highly appreciated.
- if(smsg.type=REQ_GET)
+ if(smsg.type==REQ_GET)
next time try to use -Wall when compiling, it might catch this kind of error
Symptom
I think, I messed up something, because both Mozilla Firefox and Google Chrome produce the same error: they don't receive the whole response the webserver sends them. CURL never misses, the last line of the quick-scrolling response is always "</html>".
Reason
The reason is, that I send response in more part:
sendHeaders(); // is calls sendResponse with a fix header
sendResponse(html_opening_part);
for ( ...scan some data... ) {
sendResponse(the_data);
} // for
sendResponse(html_closing_part)
The browsers stop receiving data between sendResponse() calls. Also, the webserver does not close() the socket, just at the end.
(Why I'm doing this way: the program I write is designed for non-linux system, it will run on an embedded computer. It has not too much memory, which is mostly occupied by lwIP stack. So, avoid collecting the - relativelly - huge webpage, I send it in parts. Browsers like it, no broken HTML occurred as under Linux.)
Environment
The platform is GNU/Linux (Ubuntu 32-bit with 3.0 kernel). My small webserver sends the stuff back to the client standard way:
int sendResponse(char* data,int length) {
int x = send(fd,data,length,MSG_NOSIGNAL);
if (x == -1) {
perror("this message never printed, so there's no error \n");
if (errno == EPIPE) return 0;
if (errno == ECONNRESET) return 0;
... panic() ... (never happened) ...
} // if send()
} // sendResponse()
And here's the fixed header I am using:
sendResponse(
"HTTP/1.0 200 OK\n"
"Server: MyTinyWebServer\n"
"Content-Type: text/html; charset=UTF-8\n"
"Cache-Control: no-store, no-cache\n"
"Pragma: no-cache\n"
"Connection: close\n"
"\n"
);
Question
Is this normal? Do I have to send the whole response with a single send()? (Which I'm working on now, until a quick solution arrives.)
If you read RFC 2616, you'll see that you should be using CR+LF for the ends of lines.
Aside from that, open the browser developer tools to see the exact requests they are making. Use a tool like Netcat to duplicate the requests, then eliminate each header in turn until it starts working.
Gotcha!
As #Jim adviced, I've tried sending same headers with CURL, as Mozilla does: fail, broken pipe, etc. I've deleted half of headers: okay. I've added back one by one: fail. Deleted another half of headers: okay... So, there is error, only if header is too long. Bingo.
As I've said, there're very small amount of memory in the embedded device. So, I don't read the whole request header, only 256 bytes of them. I need only the GET params and "Host" header (even I don't need it really, just to perform redirects with the same "Host" instead of IP address).
So, if I don't recv() the whole request header, I can not send() back the whole response.
Thanks for your advices, dudes!