I'm trying to build an http server using c++. and so among the conditions based in which i decide how to extract the body entity, is if there's a content length present? , here's a minimal code on how i extract body using Content-Length :
req_t *Webserver::_recv(int client_fd, bool *closed)
{
string req;
static string rest;
// string extracted_req;
char buff[1024];
// while (true) {
// std::cout << "client_fd: " << client_fd << std::endl;
int n = recv(client_fd, buff, 1024, 0);
// std::cout << "n: " << n << std::endl;
if (n == -1)
{
_set_error_code("500", "Internal Server Error");
return NULL;
}
if (n == 0)
{
*closed = true;
return NULL;
}
buff[n] = '\0';
req += buff;
req_t *extracted_req = _extract_req(client_fd, req, rest, closed);
return extracted_req;
}
...
else if (headers.find("Content-Length") != string::npos) {
string body = extract_body_len(client_fd, rest_of_req, content_length);
}
req_t is a simple struct that contains three strings status_line, headers, body.
req_t *Webserver::_extract_req(int client_fd, const string &req, string &rest, bool *closed)
{
req_t *ret;
try
{
ret = new req_t;
}
catch (std::bad_alloc &e)
{
std::cerr << "\033[1;31mError:\033[0m " << e.what() << std::endl;
exit(1);
}
string status_line = req.substr(0, req.find("\r\n"));
string headers = req.substr(req.find("\r\n") + 2, req.find("\r\n\r\n") - req.find("\r\n") - 2);
rest = req.substr(req.find("\r\n\r\n") + 4, req.size() - req.find("\r\n\r\n") - 4);
ret->status_line = status_line;
ret->headers = headers;
// if method is get request body is empty
// if the header contains a content-length, extract number of buytes for body;
if (headers.find("Content-Length") != string::npos)
{
long long content_length = _get_content_len(headers);
if (content_length == -1)
{
_set_error_code("400", "Bad Request");
return NULL;
}
// substracting the length of the body from the length of the request
ret->body = _extract_body_len(client_fd, rest, content_length, closed);
// if body is not complete, return an error
...
string extract_body_len(int client_fd, string& rest, unsigned long long len) {
string body;
unsigned long long total = 0;
body = rest;
// starting total with first bytes of body
total += rest.size();
// if we have it all that's it
if (total >= len) {
body = rest.substr(0, len);
rest = rest.substr(len);
return body;
}
else
{
while (total < len)
{
char buf[1024];
int ret = recv(client_fd, buf, 1024, 0);
// after a lot of debugging , i've noticed that recv starts to read less than 1024 only when total is closer to len, so i added this condition naively.
if (ret != 1024)
{
if ((total + ret) >= len)
{
body += string(buf).substr(0, len - total);
rest = string(buf).substr(len - total);
break;
}
}
if (ret == 0)
{
if (total == len)
{
rest = "";
break;
}
// client closed connection and it's still incomplete: 400
else
{
res->status_code = "400";
res->status_message = "Bad Request";
return NULL;
}
}
else if (ret == -1)
{
res->status_code = "500";
res->status_message = "Internal Server Error";
return body;
}
total += ret;
body += string(buf, ret);
}
}
return body;
}
Now, The problem is i've tested requests with varying sized body entities(8MB, 1.9MB, 31 MB) and all the time i never receive the whole body (as per content-length), the pattern is like the following:
recv keeps reading all 1024 bytes until total gets closer to len then it starts reading smaller numbers. until the difference between total and len is around 400...600 bytes then recv blocks at some point (there's nothing more to read) before total == len.
That really confused me, i tried with different api clients (postman, insonomia) but the same results, i doubted maybe Content-Length isn't that accurate but it obviously should be, what do you think is the problem , why am i receiving or reading less than Content-Length ?
int n = recv(client_fd, buff, 1024, 0);
The above code appears to assume that this recv call returns only the header portion of the HTTP request. Not one byte more, not one byte less.
Unfortunately, you will not find anything in your textbook on network programming that gives you any such guarantee, like that, whatsoever.
Your only guarantee (presuming that there is no socket-level error), is that recv() will return a value between 1 and 1024, representing however many bytes were already received on the socket, or arrived in the first packet that it blocked and waited for.
Using an example of a completely made up HTTP request that looks something like this:
POST /cgi-bin/upload.cgi HTTP/1.0<CR><LF>
Host: www.example.com<CR><LF>
Content-Type: application/octet-stream<CR><LF>
Content-Length: 4000<CR><LF>
<CR><LF>
[4000 octets follow]
When your web browser, or a simulated browser, sends this request this recv call can return any value between 1 and 1024 (excluding the case of network errors).
This means that this recv call can cough up anything between:
a return value of 1, and placing just the letter "P" into buff.
a return value of 1024, and placing the entire HTTP header, plus as much of the initial part of the HTTP content portion of the request into the buffer that's needed to produce 1024 bytes total.
The shown logic is completely incapable of correctly handling all of these possibilities, and that's why it fails. It will need to be reimplemented, pretty much from scratch, using the correct logic.
Related
quite new to socket programming and just had a short question, i'm trying to display multiple lines of output on my client from the server but I can't seem to get the lines to separate. Any help appreciated.
Server:
void help(int sock)
{
n = write(sock,"Commands:\n",11);
n = write(sock,"HELP -> Displays usable commands\n",34);
n = write(sock,"BROADCAST 'message' -> Sends 'message' to every user\n",54);
n = write(sock,"SEND 'user' 'message' -> Sends 'message' to 'user'\n",52);
n = write(sock,"DISPLAY -> Displays all current users\n",39);
n = write(sock,"LEAVE -> Ends the current session\n",35);
}
Client:
while(buffer[0] != 'L')
{
bzero(buffer,256);
n = read(sockfd,buffer,255);
cout << buffer << "\n";
}
There are three issues with your code:
your calls to write() are including the null terminators of the strings. You should not be that in this situation.
your read() code is ignoring the return value of read(). Just because you ask for 255 bytes does not guarantee that you will receive 255 bytes. The return value tells you how many bytes were actually received. And the bytes that you do receive are not guaranteed to be null terminated, either, so you can't write the buffer as a plain char* pointer alone to std::cout, that will make it look for a null terminator. std::cout has a write() method that you can use to specify how many chars are to be written. Use the return value of read() for that purpose.
aside from that, you are assuming that call to read() will read a single complete line, so that you can check the first char in the last received buffer for the L of the final LEAVE line. That is simply not true, and you can't rely on that. TCP is a byte stream, read() is going to receive and return arbitrary amounts of data, depending on what is available in the socket's receive buffer. If you need to read line-based data, you will have to accumulate the input bytes into a growing buffer of some kind and then scan that for line breaks as new data arrives. You can then remove only completed lines from that buffer and process their content as needed.
Try something more like this instead:
int writestr(int sock, const char *str)
{
int n, len = strlen(str);
while (len > 0)
{
n = write(sock, str, len);
if (n < 0) return n;
str += n;
len -= n;
}
return 0;
}
void help(int sock)
{
n = writestr(sock, "Commands:\n");
n = writestr(sock, "HELP -> Displays usable commands\n");
n = writestr(sock, "BROADCAST 'message' -> Sends 'message' to every user\n");
n = writestr(sock, "SEND 'user' 'message' -> Sends 'message' to 'user'\n");
n = writestr(sock, "DISPLAY -> Displays all current users\n");
n = writestr(sock, "LEAVE -> Ends the current session\n");
}
char buffer[256];
std::string data;
std::string::size_type pos, last_pos = 0;
while (true)
{
n = read(sockfd, buffer, sizeof(buffer));
if (n <= 0) break;
std::cout.write(buffer, n);
data.append(buffer, n);
pos = data.find('\n', last_pos);
if (pos != std::string::npos)
{
std::string line = data.substr(0, pos);
/* if you want to support CRLF line breaks, do this instead:
std::string::size_type len = pos;
if ((len > 0) && (data[len-1] == '\r'))
--len;
std::string line = data.substr(0, len);
*/
data.erase(0, pos+1);
if (line.compare(0, 5, "LEAVE") == 0)
break;
last_pos = 0;
}
else
last_pos = data.size();
}
I'm developing a server-client application using Winsock in c++ and have a problem.
For getting the message from the client by the server I use the code below.
int result;
char buffer[200];
while (true)
{
result = recv(client, buffer, 200, NULL);
if (result > 0)
cout << "\n\tMessage from client: \n\n\t" << message << ";";
}
I send the message "Hello" from the client to the server. However the buffer is actually this:
HelloÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ
What am I missing?
Since recv might not receive as many bytes as you told it, you typically use a function
like this to receive specified number of bytes. Modified from here
int receiveall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we've received
int bytesleft = *len; // how many we have left to receive
int n = -1;
while(total < *len) {
n = recv(s, buf+total, bytesleft, 0);
if (n <= 0) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually received here
return (n<=0)?-1:0; // return -1 on failure, 0 on success
}
It's up to you to null terminate the string if you receive string which is not null terminated.
The result tells you how many bytes were received. recv doesn't add a terminator since, in general, network data is binary data which might not be usable as a C-style string.
You can add a terminator yourself, if you know the message won't contain the termination character:
buffer[result] = 0; // make sure the buffer is large enough
or make a string (or vector, or whatever) from it:
std::string message_str(message, result);
Note that what you receive might not be a single "message", especially if you're uses a stream protocol like TCP. It might contain more than one message, or just the start of one.
memset(&receive[0], 0, sizeof(receive));
To clear the buffer
You didn't initialize your buffer
char buffer[200] = {0};
while (true)
{
result = recv(client, buffer, 200, NULL);
if (result > 0)
cout << "\n\tMessage from client: \n\n\t" << message << ";";
memset(buffer, 0, 200);
}
I assume that for messages that are of only 1 byte (a char), I will use read() and write() directly.
For those messages having size > 1 bytes, I use two subfunctions to read and write them over sockets.
For example, I have the server construct a string called strcities (list of city) and print it out --> nothing strange. Then send the number of bytes of this string to the client, and then the actual string.
The client will first read the number of bytes, then the actual city list.
For some reason my code sometimes work and sometimes doesn't. If it works, it also prints out some extra characters that I have no idea where they come from. If it doesn't, it hangs and forever waits in the client, while the server goes back to the top of the loop and wait for next command from the client. Could you please take a look at my codes below and let me know where I did wrong?
Attempt_read
string attempt_read(int rbytes) { // rbytes = number of bytes of message to be read
int count1, bytes_read;
char buffer[rbytes+1];
bool notdone = true;
count1 = read(sd, buffer, rbytes);
while (notdone) {
if (count1 == -1){
perror("Error on write call");
exit(1);
}
else if (count1 < rbytes) {
rbytes = rbytes - count1; // update remaining bytes to be read
count1 = read(sd, buffer, rbytes);
}
else {notdone = false;}
} // end while
string returnme;
returnme = string(buffer);
return returnme;
}
Attempt_write
void attempt_write(string input1, int wbytes) { // wbytes = number of bytes of message
int count1;
bool notdone = true;
count1 = write(sd, input1.c_str(), wbytes);
while (notdone) {
if (count1 == -1){
perror("Error on write call");
exit(1);
}
else if (count1 < wbytes) {
wbytes = wbytes - count1;
count1 = write(sd, input1.c_str(), wbytes);
}
else {notdone = false;}
} // end while
return;
}
1) string class has a method size() that will return the length of the string, so you do not actually need a second attempt_write parameter.
2) You can transfer length of message before message or you can transfer a terminating 0 after, if you only will sent an ASCII strings. Because your connection could terminate at any time, it is better to send exact length before sending the string, so your client could know, what to expect.
3) What compilator do you use, that would allow char buffer[rbytes+1]; ? A standard c++ would require char buffer = new char[rbytes+1]; and corresponding delete to avoid a memory leaks.
4) In your code, the second read function call use same buffer with no adjustment to length, so you, practically, overwrite the already received data and the function will only work, if all data will be received in first function call. Same goes for write function
I would suggest something like this:
void data_read(unsigned char * buffer, int size) {
int readed, total = 0;
do {
readed = read(sd, buffer + total, size - total);
if (-1 == writted) {
perror("Error on read call");
exit(1);
}
total += readed;
} while (total < size);
}
string attempt_read() {
int size = 0;
data_read((unsigned char *) &size, sizeof(int));
string output(size, (char) 0x0);
data_read((unsigned char *) output.c_str(), size);
return output;
}
void data_write(unsigned char * buffer, int size) {
int writted, total = 0;
do {
writted = write(sd, buffer + total, size - total);
if (-1 == writted) {
perror("Error on write call");
exit(1);
}
total += writted;
} while (total < size);
}
void attempt_write(string input) {
int size = input.size();
data_write((unsigned char *) &size, sizeof(int));
data_write((unsigned char *) input.c_str(), size);
}
I am trying to send large amounts of data over a socket, sometimes when I call send (on Windows) it won't send all the data I requested, as expected. So, I wrote a little function that should have solved my problems- but it's causing problems where the data isn't being sent correctly and causing the images to be corrupted. I'm making a simple chat room where you can send images (screenshots) to each other.
Why is my function not working?
How can I make it work?
void _internal_SendFile_alignment_512(SOCKET sock, BYTE *data, DWORD datasize)
{
Sock::Packet packet;
packet.DataSize = datasize;
packet.PacketType = PACKET_FILETRANSFER_INITIATE;
DWORD until = datasize / 512;
send(sock, (const char*)&packet, sizeof(packet), 0);
unsigned int pos = 0;
while( pos != datasize )
{
pos += send(sock, (char *)(data + pos), datasize - pos, 0);
}
}
My receive side is:
public override void OnReceiveData(TcpLib.ConnectionState state)
{
if (state.fileTransfer == true && state.waitingFor > 0)
{
byte[] buffer = new byte[state.AvailableData];
int readBytes = state.Read(buffer, 0, state.AvailableData);
state.waitingFor -= readBytes;
state.bw.Write(buffer);
state.bw.Flush();
if (state.waitingFor == 0)
{
state.bw.Close();
state.hFile.Close();
state.fileTransfer = false;
IPEndPoint ip = state.RemoteEndPoint as IPEndPoint;
Program.MainForm.log("Ended file transfer with " + ip);
}
}
else if( state.AvailableData > 7)
{
byte[] buffer = new byte[8];
int readBytes = state.Read(buffer, 0, 8);
if (readBytes == 8)
{
Packet packet = ByteArrayToStructure<Packet>(buffer);
if (packet.PacketType == PACKET_FILETRANSFER_INITIATE)
{
IPEndPoint ip = state.RemoteEndPoint as IPEndPoint;
String filename = getUniqueFileName("" + ip.Address);
if (filename == null)
{
Program.MainForm.log("Error getting filename for " + ip);
state.EndConnection();
return;
}
byte[] data = new byte[state.AvailableData];
readBytes = state.Read(data, 0, state.AvailableData);
state.waitingFor = packet.DataSize - readBytes;
state.hFile = new FileStream(filename, FileMode.Append);
state.bw = new BinaryWriter(state.hFile);
state.bw.Write(data);
state.bw.Flush();
state.fileTransfer = true;
Program.MainForm.log("Initiated file transfer with " + ip);
}
}
}
}
It receives all the data, when I debug my code and see that send() does not return the total data size (i.e. it has to be called more than once) and the image gets yellow lines or purple lines in it — I suspect there's something wrong with sending the data.
I mis-understood the question and solution intent. Thanks #Remy Lebeau for the comment to clarify that. Based on that, you can write a sendall() function as given in section 7.3 of http://beej.us/guide/bgnet/output/print/bgnet_USLetter.pdf
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we've sent
int bytesleft = *len; // how many we have left to send
int n = 0;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
if (n == -1) {
/* print/log error details */
break;
}
total += n;
bytesleft -= n;
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}
You need to check the returnvalue of send(). In particular, you can't simply assume that it is the number of bytes sent, there is also the case that there was an error. Try this instead:
while(datasize != 0)
{
n = send(...);
if(n == SOCKET_ERROR)
throw exception("send() failed with errorcode #" + to_string(WSAGetLastEror()));
// adjust pointer and remaining number of bytes
datasize -= n;
data += n;
}
BTW:
Make that BYTE const* data, you're not going to modify what it points to.
The rest of your code seems too complicated, in particular you don't solve things by aligning to magic numbers like 512.
what is the right way to read chunked data (from http request) from socket?
sf::TcpSocket socket;
socket.connect("0.0.0.0", 80);
std::string message = "GET /address HTTP/1.1\r\n";
socket.send(message.c_str(), message.size() + 1);
// Receive an answer from the server
char buffer[128];
std::size_t received = 0;
socket.receive(buffer, sizeof(buffer), received);
std::cout << "The server said: " << buffer << std::endl;
But server sends infinite data and socket.receive doesn't return management. Any right ways to read chunked data part by part? (The answer is chunked data).
The right way to process HTTP requests is to use a higher-level library that manages the socket connections for you. In C++ one example would be pion-net; there are others too like Mongoose (which is C, but fine to use in C++).
Well infinite data is theoretically possible while the practical implementation differ from process to process.
Approach 1 - Generally many protocol do send size in the first few bytes ( 4 bytes ) and you can have a while loop
{
int i = 0, ret = 1;
unsigned char buffer[4];
while ( i<4 && ret == 0)
socket.receive(buffer + i, 1 , ret);
// have a while loop to read the amount of data you need. Malloc the buffer accordingly
}
Approach 2 - Or in your case where you don't know the lenght ( infinite )
{
char *buffer = (char *)malloc(TCP_MAX_BUF_SIZE);
std::size_t total = 0, received = 0;
while ( total < TCP_MAX_BUF_SIZE && return >= 0) {
socket.receive(buffer, sizeof(buffer), received);
total += received;
}
//do something with your data
}
You will have to break at somepoint and process your data Dispatch it to another thread of release the memory.
If by "chunked data" you are referring to the Transfer-Encoding: chunked HTTP header, then you need to read each chunk and parse the chunk headers to know how much data to read in each chunk and to know when the last chunk has been received. You cannot just blindly call socket.receive(), as chunked data has a defined structure to it. Read RFC 2616 Section 3.6.1 for more details.
You need to do something more like the following (error handling omitted for brevity - DON'T omit it in your real code):
std::string ReadALine(sf::TcpSocket &socket)
{
std::string result;
// read from socket until a LF is encountered, then
// return everything up to, but not including, the
// LF, stripping off CR if one is also present...
return result;
}
void ReadHeaders(sf::TcpSocket &socket, std::vector<std::string> &headers)
{
std::string line;
do
{
line = ReadALine(socket);
if (line.empty()) return;
headers.push_back(line);
}
while (true);
}
std::string UpperCase(const std::string &s)
{
std::string result = s;
std::for_each(result.begin(), result.end(), toupper);
return result;
}
std::string GetHeader(const std::vector<std::string> &headers, const std::string &s)
{
std::string prefix = UpperCase(s) + ":";
for (std::vector<std::string>::iterator iter = headers.begin(), end = headers.end(); iter != end; ++iter)
{
if (UpperCase(i)->compare(0, prefix.length(), prefix) == 0)
return i->substr(prefix.length());
}
return std::string();
}
sf::TcpSocket socket;
socket.connect("0.0.0.0", 80);
std::string message = "GET /address HTTP/1.1\r\nHost: localhost\r\n\r\n";
socket.send(message.c_str(), message.length());
std:vector<std::string> headers;
std::string statusLine = ReadALine(sockeet);
ReadHeaders(socket, headers);
// Refer to RFC 2616 Section 4.4 for details about how to properly
// read a response body in different situations...
int statusCode;
sscanf(statusLine.c_str(), "HTTP/%*d.%*d %d %*s", &statusCode);
if (
((statusCode / 100) != 1) &&
(statusCode != 204) &&
(statusCode != 304)
)
{
std::string header = GetHeader(headers, "Transfer-Encoding");
if (UpperCase(header).find("CHUNKED") != std::string::npos)
{
std::string extensions;
std::string_size_type pos;
std::size_t chunkSize;
do
{
line = ReadALine(socket);
pos = line.find(";");
if (pos != std::string::npos)
{
extensions = line.substr(pos+1);
line.resize(pos);
}
else
extensions.clear();
chunkSize = 0;
sscanf(UpperCase(line).c_str(), "%X", &chunkSize);
if (chunkSize == 0)
break;
socket.receive(someBuffer, chunkSize);
ReadALine(socket);
// process extensions as needed...
// copy someBuffer into your real buffer...
}
while (true);
std::vector<std::string> trailer;
ReadHeaders(socket, trailer);
// merge trailer into main header...
}
else
{
header = GetHeader(headers, "Content-Length");
if (!header.empty())
{
uint64_t contentLength = 0;
sscanf(header.c_str(), "%Lu", &contentLength);
// read from socket until contentLength number of bytes have been read...
}
else
{
// read from socket until disconnected...
}
}
}