using curllib to send data to server - sockets

I am very new to all this http thing and I am trying to make communication with a server.
I need to write the client that post to server - I know what server returns but I am not sure how to send it the data:
I (the client) Post XML (binary format) to server at url: 192.168.2.111/senddata (i.e post /senddata) using HTTP.
Server response is:
TCP: [ACK] Seq=1274 Ack=504 Win=525088 Len=0
I.E here is the problem - the server does not reply with HTTP reply.
The protocol server uses after I send it the XML is TCP and this makes curl_easy_perform
to not return.
After server TCP reply in step 2, I-the client should send binary data in a loop to server using HTTP Continuation or non-HTTP traffic.
Server asynchronicity answers with
TCP: [ACK] Seq=1274 Ack=3424 Win=522176 Len=0
for each binary data sending .
So to send the XML in step 1, I use
curl_easy_setopt(mCurlHndl, CURLOPT_URL, "192.168.2.111");
curl_easy_setopt(mCurlHndl, CURLOPT_CUSTOMREQUEST, "POST /senddata");
curl_easy_setopt(mCurlHndl, CURLOPT_POST, 1L);
curl_easy_setopt(mCurlHndl, CURLOPT_POSTFIELDS, sXML.c_str());
curl_easy_setopt(mCurlHndl, CURLOPT_POSTFIELDSIZE, sXML.length());
headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
headers = curl_slist_append(headers, "Accept:");
headers = curl_slist_append(headers, (string("Content-Length: ") + string(to_string(sXML.length()))).c_str());
curl_easy_setopt(mCurlHndl, CURLOPT_HTTPHEADER, headers);
res = curl_easy_perform(mCurlHndl);
Server does send in reply the:
TCP: [ACK] Seq=1274 Ack=504 Win=525088 Len=0
(just with other seq/ack/win numbers of course).
OK, here starts the problems.
a. curl_easy_perform does not return since (I think) server reply was not HTTP, but TCP.
b. OK, lets say that i can bypass it giving Timeout
curl_easy_setopt(mCurlHndl, CURLOPT_TIMEOUT_MS, 1000);
But i just know that i am doing something wrong.
c. Lets say I gave the timeout, now in step 3 I need to send in a loop stream of data using HTTP Continuation or non-HTTP traffic.
I do not know how to do that since it should not be regular post again like in step 1.
It should be HTTP Continuation or non-HTTP traffic.
Sorry for my bad explanation but again it is from my very little experience in HTTP.
Seems that after step 1, server breaks that normal http connection rules and what I need to send it is the binary data in a loop but again, i do not know how to do step 2 and 3. I.E how to get the TCP ACK from server using curllib that I will know to move to step 3 and how to (step 3) send to server over the same connection (I think) the binary data in loop.
I want to add that servers returns what is supposed to return. in step 2 server should return the TCP: [ACK] Seq=1274 Ack=504 Win=525088 Len=0 since this is what server should do - this is that server protocol. It is not that i do not send it right (in step 1) and that is why it send TCP reply and not http.
Here is Wireshark log:
192.168.2.7 is the client (that works).
192.168.2.111 is the server.
in the first line (seq 9) the clinet post the binary xml (POST /senddata) and then the loop of binary data stream sending starts as you can see:
9 0.173574000 192.168.2.7 192.168.2.111 HTTP 380 POST /senddata HTTP/1.1 (application/octet-stream)
10 0.176611000 192.168.2.111 192.168.2.7 TCP 60 4089 > 53419 [ACK] Seq=1274 Ack=504 Win=525088 Len=0
11 0.182581000 192.168.2.111 192.168.2.7 UDP 90 Source port: 4020 Destination port: 4005
12 0.553367000 192.168.2.7 192.168.2.111 HTTP 1354 Continuation or non-HTTP traffic
13 0.553403000 192.168.2.7 192.168.2.111 HTTP 1354 Continuation or non-HTTP traffic
14 0.555539000 192.168.2.111 192.168.2.7 TCP 60 4089 > 53419 [ACK] Seq=1274 Ack=3424 Win=522176 Len=0
15 0.555698000 192.168.2.7 192.168.2.111 HTTP 1354 Continuation or non-HTTP traffic
16 0.555724000 192.168.2.7 192.168.2.111 HTTP 1354 Continuation or non-HTTP traffic
17 0.555724000 192.168.2.7 192.168.2.111 HTTP 1354 Continuation or non-HTTP traffic
18 0.555724000 192.168.2.7 192.168.2.111 HTTP 1354 Continuation or non-HTTP traffic
19 0.558870000 192.168.2.111 192.168.2.7 TCP 60 4089 > 53419 [ACK] Seq=1274 Ack=6344 Win=519248 Len=0
20 0.559033000 192.168.2.7 192.168.2.111 HTTP 1354 Continuation or non-HTTP traffic
..... and many other sends like seq 12 - seq 20 ........
How do i do something like this with curllib - this is my question.
Thanks again

You are not using curl correctly for sending an HTTP POST request. Try this instead:
curl_easy_setopt(mCurlHndl, CURLOPT_URL, "http://192.168.2.111/senddata");
curl_easy_setopt(mCurlHndl, CURLOPT_POST, 1L);
curl_easy_setopt(mCurlHndl, CURLOPT_POSTFIELDS, sXML.c_str());
curl_easy_setopt(mCurlHndl, CURLOPT_POSTFIELDSIZE, sXML.length());
struct curl_slist *headers = curl_slist_append(NULL, "Content-Type: application/octet-stream");
headers = curl_slist_append(headers, "Accept:");
curl_easy_setopt(mCurlHndl, CURLOPT_HTTPHEADER, headers);
res = curl_easy_perform(mCurlHndl);
curl_slist_free_all(headers);
// process reply using curl_easy_getinfo() and curl_easy_recv() as needed...
With that said, are you sure that you should be sending the Content-Type as application/octet-stream? Typically XML is posted using text/xml, application/xml, or other XML-related Content-Type so the server knows it is dealing with XML data. application/octet-stream refers to arbitrary binary data when its actual type is not known.
Also, setting the Accept header to a blank value basically means you do not accept any data in reply. Unless you want to accept only specific Content-Type reply types, or want to accept any data the server wants to send, then you should omit the Accept header.
Read RFC 2616 for the formal definition of the HTTTP protocol and how it works.
Update: since you are obviously dealing with a custom protocol and not standard HTTP, you cannot use curl_easy_perform() to send the HTTP request. You will have to enable the CURLOPT_CONNECT_ONLY option so curl_easy_perform() will only open the socket connection, and then use curl_easy_send() to send the actual request and subsequent binary data, eg:
CURLcode curl_send(CURL *curl, const void * buffer, size_t buflen)
{
size_t sent;
uint8_t *pbuf = (uint8_t*) buffer;
while (buflen > 0)
{
do
{
res = curl_easy_send(curl, pbuf, buflen, &sent);
}
while (res == CURLE_AGAIN);
if (res != CURLE_OK)
return res;
pbuf += sent;
buflen -= sent;
}
while (buflen > 0);
return CURLE_OK;
}
mCurlHndl = curl_easy_init();
...
curl_easy_setopt(mCurlHndl, CURLOPT_URL, "http://192.168.2.111");
curl_easy_setopt(mCurlHndl, CURLOPT_CONNECT_ONLY, 1);
res = curl_easy_perform(mCurlHndl);
if (res == CURLE_OK)
{
string req =
"POST /senddata HTTP/1.1\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Length: " + to_string(sXML.length()) + "\r\n"
"\r\n"
+ sXML;
res = curl_send(mCurlHndl, req.c_str(), req.length());
if (res !- CURLE_OK) ...
res = curl_send(mCurlHndl, ...binary data...);
if (res !- CURLE_OK) ...
res = curl_send(mCurlHndl, ...binary data...);
if (res !- CURLE_OK) ...
res = curl_send(mCurlHndl, ...binary data...);
if (res !- CURLE_OK) ...
...
}
...
curl_easy_cleanup(mCurlHndl);

Related

Play framework 2.5 will not stream the request into the response

Hi I have a requirement to use play framework 2.5 (scala) to receive a large request body, then transform it and then stream it straight back out.
So far I've been unable to get the request stream to be sent out correctly using a chunked response (even untransformed).
Code example:
def endpointA = EssentialAction { requestHeader =>
Accumulator.source.map { source: Source[ByteString, _] =>
Ok.chunked(source)
}
}
POSTing data to the endpoint with curl does not output the posted data as expected and just results in the error below. I confirmed with wireshark that no response body is sent.
curl -v --data 'hello' -H "Connection: Keep-Alive" -H "Keep-Alive: 300" -H "Content-type: text/plain" http://localhost:9584/binding-tariff-admin/upload-csv
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9584 (#0)
> POST /binding-tariff-admin/endpoint-a HTTP/1.1
> Host: localhost:9584
> User-Agent: curl/7.64.1
> Accept: */*
> Connection: Keep-Alive
> Keep-Alive: 300
> Content-type: text/plain
> Content-Length: 5
>
* upload completely sent off: 5 out of 5 bytes
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Cache-Control: no-cache,no-store,max-age=0
< Content-Security-Policy: default-src 'self' 'unsafe-inline' *.s3.amazonaws.com www.google-analytics.com data:
< X-Permitted-Cross-Domain-Policies: master-only
< Content-Type: application/octet-stream
< Date: Wed, 19 Feb 2020 10:15:36 GMT
<
* transfer closed with outstanding read data remaining
* Closing connection 0
curl: (18) transfer closed with outstanding read data remaining
Also, if I change the code to return a stream I create myself, it works fine:
val testStream: Source[ByteString, NotUsed] = Source(List("hello")).map(ByteString.apply)
Is there something fundamentally wrong with what I'm trying to do here? I have seen other stack overflow examples with people suggesting this should be possible, e.g.
Play Framework Scala: How to Stream Request Body
I also tried using the verbatimBodyParser method described in the link but got the same results.
Thanks!
NFV

Is it possible to add content to the body of a webpage with a second http request from the server

I would like to create a webserver which sends the content he fetched from a socket server to a browser client. For this I have to fetch a char from the socket server and send it directly to the client (Webbrowser). I am sending the html body using the following headers:
html = "hello world\r\n";
headers << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: text/html\r\n"
<< "Content-Length: " << html.length() << "\r\n"
<< "Connection: close\r\n"
<< "Server: CPi\r\n\r\n"
<< html; // the body of the request
server.sendMessage(client, headers.str());
but I would like to keep the connection alive and add something to the body with a second request like this:
html1 = "hello \r\n"
headers << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: text/html\r\n"
<< "Content-Length: " << html1.length() << "\r\n" // "hello " has 6 chars
//<< "Connection: close\r\n" keep the connection alive
<< "Server: CPi\r\n\r\n"
<< html1; // !! send only the first part
server.sendMessage(client, headers.str());
headers.str(""); // clear header data
html2 = "world\r\n";
headers << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: text/html\r\n"
<< "Content-Length: " << html2.length() << "\r\n"
<< "Connection: close\r\n" // close the connection because the body is complete
<< "Server: CPi\r\n\r\n"
<< html2; // !! append the second part to the body
server.sendMessage(client, headers.str());
But it seems not to work. Is there a way to do it?
But it seems not to work.
HTTP is a protocol where one request has one response. You are trying to send two responses for a single request and the client will simply not expect the second response. This means it will be either ignored or it erroneously will be taken as the response to another request the client is sending on the same TCP connection.
Is there a way to do it?
It is not fully clear to me what you are trying to achieve with this at the end.
But if your problem is that you don't have the full response at once and that you want to send it to the client in pieces you can use for example chunked transfer encoding of the body.

Docker API returns 200 OK then 400 BAD REQUEST

I am writing an API client for Docker. I understood from the documentation that the API is Restful/HTTP, yet if you connect to the local daemon you have to do it over the exposed unix socket.
It all seems to work, I open a socket, send an HTTP request (which respects the specification), I receive the expected response, but also a 400 BAD REQUEST response follows immediately.
Here is the request:
GET /info HTTP/1.1
Host: localhost
Accept: application/json
And here is what I get:
HTTP/1.1 200 OK
Api-Version: 1.30
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/17.06.1-ce (linux)
Date: Thu, 01 Feb 2018 18:53:18 GMT
Transfer-Encoding: chunked
892
{"ID":"6MGE:35TO:BI..." ...}
0
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close
400 Bad Request
First, I figured that there is a bug on my side and I am somehow sending 2 requests, but I enabled debugging and followed the logs with sudo journalctl -fu docker.service and there is exactly one request received... at least one is logged, the GET /info. I've also debugged the code and 1 single request is sent.
Any hint is greatly appreciated!
Edit: here is the client's code:
final StringBuilder hdrs = new StringBuilder();
for(final Map.Entry<String, String> header : headers) {
hdrs.append(header.getKey() + ": " + header.getValue())
.append("\r\n");
}
final String request = String.format(
this.template(), method, home, hdrs, this.readContent(content)
);
final UnixSocketChannel channel = UnixSocketChannel.open(
new UnixSocketAddress(this.path)
);
final PrintWriter writer = new PrintWriter(
Channels.newOutputStream(channel)
);
writer.print(request);
writer.flush();
final InputStreamReader reader = new InputStreamReader(
Channels.newInputStream(channel)
);
CharBuffer result = CharBuffer.allocate(1024);
reader.read(result);
result.flip();
System.out.println("read from server: " + result.toString());
It seems like you have an extra CRLF between headers and body.
private String template() {
final StringBuilder message = new StringBuilder();
message
.append("%s %s HTTP/1.1\r\n")
.append("Host: localhost").append("\r\n")
.append("%s")
.append("\r\n").append("\r\n") //one of these is superfluous, as each header line ends with "\r\n" itself
.append("%s");
return message.toString();
}
Remove one append("\r\n") after headers and see what happens.
Fixed. Initially, I thought the problem was with the line endings (that they should have been \n instead of \r\n). Turns out, the 400 BAD REQUEST occured because the Connection: close header was missing, while the Request made was being closed right after receiving the response.
More details here.

nginx lua not able to set headers before rewrite

I have a set of ip address in my redis server to be blocked. Now when a client makes a request,
the request must be intercepted by nginx
check if the remote_addr belongs to the blocked ip
add a header if the ip is blocked
then redirect to the actual ip address with the request_uri.
nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
lua_shared_dict ip_status 1m;
server {
listen 9080;
server_name localhost;
location ~ .* {
rewrite_by_lua_file src/ip_check.lua;
}
}
}
src/ip_check.lua
-- redis configuration
local redis_host = "127.0.0.1"
local redis_port = 6379 -- connection timeouts for redis in ms.
local redis_max_idle_timeout = 10000
local redis_pool_size = 2 -- don't set this too high!
local redis_timeout = 200
-- check a set with this key for blacklist entries
local redis_key = ngx.var.remote_addr
local ip_status = ngx.shared.ip_status
local status_unblocked = "0"
local status_blocked = "1"
-- cache lookups for this many seconds
local cache_ttl = 1800
local redirect_host = "http://192.168.12.103:8080/Spring4MVCHelloWorld1"
local header_ip_status_key = "Ip-Status"
-- value of the header to be sent when the client ip address is blocked
local header_ip_status_value = "block"
local add_header = status_unblocked
-- lookup the value in the cache
local cache_result = ip_status:get(redis_key)
if cache_result then
if cache_result == status_blocked then
add_header = status_blocked
end
else
-- lookup against redis
local resty = require "resty.redis"
local redis = resty:new()
redis:set_timeout(redis_timeout)
local connected, err = redis:connect(redis_host, redis_port)
if not connected then
ngx.log(ngx.ERR, "ip_check: could not connect to redis #"..redis_host..":"..redis_port.." - "..err)
else
ngx.log(ngx.ALERT, "ip_check: found connect to redis #"..redis_host..":"..redis_port.." - successful")
local result, err = redis:get(redis_key)
if not result then
ngx.log(ngx.ERR, "ip_check: lookup failed for "..ngx.var.remote_addr.." - "..err)
else
if result == status_blocked then
add_header = status_blocked
end
-- cache the result from redis
ip_status:set(ip, add_header, cache_ttl)
end
redis:set_keepalive(redis_max_idle_timeout, redis_pool_size)
end
end
ngx.log(ngx.ALERT, "ip_check: "..header_ip_status_key.." of "..ngx.var.remote_addr.." is "..add_header)
if add_header == status_blocked then
ngx.header[header_ip_status_key] = header_ip_status_value
ngx.req.set_header(header_ip_status_key, header_ip_status_value)
end
ngx.redirect(redirect_host..ngx.var.request_uri)
For testing purpose, i added 127.0.0.1 key to redis with value 1. So, the redirect uri should be hit with the additional header. The problem i'm facing is no matter what i use ngx.header or ngx.req.set_header the Ip-Status header is not sent to the redirect request and the end api does not recieve it.
For example if i hit http://localhost:9080/hello in the browser,
Request headers
Host:"localhost:9080"
User-Agent:"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0"
Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
Accept-Language:"en-US,en;q=0.5"
Accept-Encoding:"gzip, deflate"
Connection:"keep-alive"
Response headers
Connection:"keep-alive"
Content-Length:"166"
Content-Type:"text/html"
Date:"Thu, 05 May 2016 08:06:33 GMT"
Location:"http://192.168.12.103:8080/Spring4MVCHelloWorld1/hello"
Server:"openresty/1.9.7.2"
ip-status:"block"
The redirected uri is http://localhost:9080/hello,
Request headers
Host:"192.168.12.103:8080"
User-Agent:"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0"
Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
Accept-Language:"en-US,en;q=0.5"
Accept-Encoding:"gzip, deflate"
Cookie:"JSESSIONID=4834843FE0E76170E429028E096A66E5"
Connection:"keep-alive"
Response headers
Content-Language:"en-US"
Content-Length:"166"
Content-Type:"text/html;charset=UTF-8"
Date:"Thu, 05 May 2016 08:06:33 GMT"
Server:"Apache-Coyote/1.1"
I'm able to see Ip-Status header in response headers of the original request but not in the request headers of the redirected uri. Any help as to how to send header to the redirected uri will be very helpful.
I'm new to nginx and lua. Asking because i couldn't find any corresponding questions apologies if the question is already asked.
It is the browser that is redirecting to the redirected uri and nginx do not have control on the headers. So, i removed
ngx.redirect(redirect_host..ngx.var.request_uri)
from the src/ip_check.lua and changed nginx.conf to make a proxy call and i was able to observe that the api was able to receive the additional header.
Modified nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
lua_shared_dict ip_status 1m;
server {
listen 9080;
server_name localhost;
location ~ .* {
set $backend_host "http://192.168.12.103:8080/Spring4MVCHelloWorld1";
access_by_lua_file src/ip_check.lua;
proxy_pass $backend_host$request_uri;
}
}
}
This modified nginx.conf will make a request to $backend_host$request_uri and browser will not have any knowledge of the redirections made. Hence, the headers set by ngx.req.set_header will be sent when making the proxy call. So,
ngx.header[header_ip_status_key] = header_ip_status_value
can also be removed from src/ip_check.lua.

logging in to server using Socket Programming

I am trying to write a code that connect our local server, log in to a webpage on that and retrieve some data. I could connect the server using the server IP and connect function. Now I need to log in on a webpage that accept the following format:
addUPI?function=login&user=user-name&passwd=user-password&host-id=xxxx&mode=t/z
I wrote something like this:
int ret= send(sock,"addUPI?funcion...&mode=t",strlen("addUPI?funcion...&mode=t"),0);
but it does not work. Can anybody help me please?
This isn't really the right way to do HTTP. For one thing, the typical HTTP lifecycle looks something like this (very abbreviated):
...Connect
>>> GET / HTTP/1.0
>>> Host: localhost
>>> Referrer: http://www.google.com
>>>
<<< HTTP/1.0 200 OK
<<< Date: Wed, 08 Apr 2015 05:21:32 GMT
<<< Content-Type: text/html
<<< Content-Length: 20
<<< Set-Cookie: ...
<<<
<<< <html><h1>Hello World</h1></html>
And that's assuming there are no redirects, SSL or other mystical protocol happenings. So, just writing the string you specified above is going to result in a closed connection due to not following the protocol.
Really, you probably want to use a fully-baked HTTP library like cURL, which manages all the protocol requirements.
I shamelessly adapted this example from the curl website:
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com/addUPI?function=login&user=user-name&passwd=user-password&host-id=xxxx&mode=t");
/* example.com is redirected, so we tell libcurl to follow redirection */
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
}
return 0;
}