What can cause Rust's TcpSocket::write() to return "invalid input"? - sockets

For a little fun I wanted to make a simple HTTP request in Rust. I threw this together and it works great:
use std::io::TcpStream;
fn main() {
// This just does a "GET /" to www.stroustrup.com
println!("Establishing connection...");
let mut stream = TcpStream::connect("www.stroustrup.com:80").unwrap();
println!("Writing HTTP request...");
// unwrap() the result to make sure it succeeded, at least
let _ = stream.write(b"GET / HTTP/1.1\r\n\
Host: www.stroustrup.com\r\n\
Accept: */*\r\n\
Connection: close\r\n\r\n").unwrap();
println!("Reading response...");
let response = stream.read_to_string().unwrap();
println!("Printing response:");
println!("{}", response);
}
Response is:
Establishing connection...
Writing HTTP request...
Reading response...
Printing response:
HTTP/1.1 200 OK
...and the rest of the long HTTP response with all the HTML as I'd expect...
However, if I change the request to be /C++.html instead of /:
use std::io::TcpStream;
fn main() {
// The only change is to "GET /C++.html" instead of "GET /"
println!("Establishing connection...");
let mut stream = TcpStream::connect("www.stroustrup.com:80").unwrap();
println!("Writing HTTP request...");
// unwrap() the result to make sure it succeeded, at least
let _ = stream.write(b"GET /C++.html HTTP/1.1\r\n\
Host: www.stroustrup.com\r\n\
Accept: */*\r\n\
Connection: close\r\n\r\n").unwrap();
println!("Reading response...");
let response = stream.read_to_string().unwrap();
println!("Printing response:");
println!("{}", response);
}
The socket returns "invalid input":
Establishing connection...
Writing HTTP request...
Reading response...
thread '<main>' panicked at 'called `Result::unwrap()` on an `Err` value: invalid input', /Users/rustbuild/src/rust-buildbot/slave/nightly-dist-rustc-mac/build/src/libcore/result.rs:746
Why does the socket return "invalid input"? The TCP socket isn't aware of the HTTP protocol (and I've tested my request with telnet and netcat: it's correct), so it can't be complaining about HTTP request/response.
What does "invalid input" even mean here? Why doesn't this work?
My rust version (I'm on OS X 10.10.1):
$ rustc --version
rustc 1.0.0-nightly (ea6f65c5f 2015-01-06 19:47:08 +0000)

The "invalid input" error isn't coming from the socket. It's coming from String. If the read_to_string() call is changed to read_to_end(), then the response is successful. Apparently the response isn't valid UTF-8.
More explicitly, the code:
println!("Reading response...");
let response = stream.read_to_end().unwrap();
println!("Printing response:");
println!("{}", String::from_utf8(response));
returns:
Err(invalid utf-8: invalid byte at index 14787)
So the HTTP response is bad. Looking at the web page, the error is here (the � characters are the problem):
Lang.Next'14 Keynote: What � if anything � have we learned from C++?

The offending characters are 0x96, indeed invalid utf-8. It should be U+2013 –
The document is either iso-8859-1 or windows 1252. There are a number of other problems with that HTML, such as unescaped &'s.

Related

RESTful client in Unity - validation error

I have a RESTful server created with ASP.Net and am trying to connect to it with the use of a RESTful client from Unity. GET works perfectly, however I am getting a validation error when sending a POST request. At the same time both GET and POST work when sending requests from Postman.
My Server:
[HttpPost]
public IActionResult Create(User user){
Console.WriteLine("***POST***");
Console.WriteLine(user.Id+", "+user.sex+", "+user.age);
if(!ModelState.IsValid)
return BadRequest(ModelState);
_context.Users.Add(user);
_context.SaveChanges();
return CreatedAtRoute("GetUser", new { id = user.Id }, user);
}
My client:
IEnumerator PostRequest(string uri, User user){
string u = JsonUtility.ToJson(user);
Debug.Log(u);
using (UnityWebRequest webRequest = UnityWebRequest.Post(uri, u)){
webRequest.SetRequestHeader("Content-Type","application/json");
yield return webRequest.SendWebRequest();
string[] pages = uri.Split('/');
int page = pages.Length - 1;
if (webRequest.isNetworkError || webRequest.isHttpError){
Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text);
}
else{
Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text);
}
}
}
I was trying both with the Json conversion and writing the string on my own, also with the WWWForm, but the error stays.
The error says that it's an unknown HTTP error. When printing the returned text it says:
"One or more validation errors occurred.","status":400,"traceId":"|b95d39b7-4b773429a8f72b3c.","errors":{"$":["'%' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0."]}}
On the server side it recognizes the correct method and controller, however, it doesn't even get to the first line of the method (Console.WriteLine). Then it says: "Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ValidationProblemDetails'".
Here're all of the server side messages:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 POST http://localhost:5001/user application/json 53
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'TheNewestDbConnect.Controllers.UserController.Create (TheNewestDbConnect)'
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
Route matched with {action = "Create", controller = "User"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Create(TheNewestDbConnect.Data.Entities.User) on controller TheNewestDbConnect.Controllers.UserController (TheNewestDbConnect).
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ValidationProblemDetails'.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
Executed action TheNewestDbConnect.Controllers.UserController.Create (TheNewestDbConnect) in 6.680400000000001ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'TheNewestDbConnect.Controllers.UserController.Create (TheNewestDbConnect)'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 11.3971ms 400 application/problem+json; charset=utf-8
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
I have no idea what is happening and how to solve it. Any help will be strongly appreciated!
Turned out I was just missing an upload handler. Adding this line solved it: webRequest.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(JsonObject));

Sending data to socket (after reading) only occurs when the socket connection ends

I'm trying to write a very basic webserver in Haskell. This is my code:
{-# LANGUAGE OverloadedStrings #-}
import Network (withSocketsDo, listenOn, PortID(..))
import Network.Socket (Socket, accept, close, setSocketOption, SocketOption(..))
import Network.Socket.ByteString (send, sendAll, recv)
import Control.Concurrent.Async (async)
import Control.Monad (forever)
import Data.ByteString.Char8 (unpack)
import Request
main = withSocketsDo $ do
sock <- listenOn $ PortNumber 3000
putStrLn "Listening on port 3000..."
forever $ do
(conn, _) <- accept sock
async $ handleAccept conn
handleAccept :: Socket -> IO ()
handleAccept sock = do
putStrLn $ "Connected!"
rawReq <- recv sock 4096
let req = parseRawRequest $ unpack rawReq -- returns Maybe Request
putStrLn $ show req
handleRequest sock req
handleRequest :: Socket -> Maybe Request -> IO ()
handleRequest sock Nothing = do
putStrLn "Closing..."
handleRequest sock req = do
sendAll sock "In handleRequest!" -- Doesn't appear until server is killed.
This is what I expected to happen:
Start server.
"Listening on port 3000..." is printed on server-side.
Do curl localhost:3000
"Connected!" is printed server-side.
The request is printed server-side.
"In handleRequest!" is printed.
What actually happens:
Start server.
"Listening on port 3000..." is printed on server-side.
Do curl localhost:3000
"Connected!" is printed server-side.
The request is printed server-side.
I wait patiently
I kill the server with CTRL+C
"In handleRequest!" prints client-side.
I suspect this has something to do with possible laziness in recv, although I use the value immediately afterwards (I parse the raw request into a Request type), so theoretically it should be evaluated.
If I put sendAll sock "Yadda yadda at the end of handleAccept, everything works fine. It's when I move this behaviour into a new function, handleRequest, that things go wonky.
Any thoughts? I'm new-ish to Haskell, so I'd appreciate any comments on the issue, or my code generally.
Cheers.
EDIT:
This is super weird! I "fixed" it, but I have no idea why this occurs.
This is the line that only appeared after I killed the server:
handleRequest sock req = do
sendAll sock "In handleRequest!" -- Doesn't appear until server is killed.
If I intentionally close the socket after sending, it works:
handleRequest sock req = do
sendAll sock "In handleRequest!" -- Now appears without killing the server
close sock
So it sends when the connection is closed. This is consistent with previous behaviour, since the connection automatically closes when the server is killed.
Now for the confusing bit. If I replace it with:
handleRequest sock req = do
sendAll sock "In handleRequest!\n" -- Works perfect
This works without closing the connection! It does what I expected, just by adding a newline. Why does this occur?
What on earth? Is it a printing problem with my terminal, not the code? (OSX iTerm2)
EDIT 2:
Was asked to provide the code for my Request module:
import Data.List (isInfixOf)
import Data.List.Split (splitOn)
data RequestType = GET | PUT
deriving Show
data Request =
Request {
reqType :: RequestType,
path :: String,
options :: [(String, String)]
} deriving Show
-- Turn a raw HTTP request into a request
-- object.
parseRawRequest :: String -> Maybe Request
parseRawRequest rawReq =
Request <$> parseRawRequestType rawReq
<*> parseRawRequestPath rawReq
<*> parseRawRequestOps rawReq
-- Turn an (entire) raw HTTP request into just
-- the request type.
parseRawRequestType :: String -> Maybe RequestType
parseRawRequestType rawReq =
case typ of
"GET" -> Just GET
"PUT" -> Just PUT
_ -> Nothing
where typ = (head . words . head . lines) rawReq
-- Turn an (entire) raw HTTP request into just
-- the path.
parseRawRequestPath :: String -> Maybe String
parseRawRequestPath = Just . (!! 1) . words . head . lines
-- Turn an (entire) raw HTTP request into just
-- a lookup table of their options.
parseRawRequestOps :: String -> Maybe [(String, String)]
parseRawRequestOps rawReq = Just [("One", "Two")] -- Test impl
I have one answer and one suggestion.
The suggestion is for you to turn off the naggle algorithm after accept:
setSocketOption conn NoDelay 1
The answer is that your sendAll is sending data but curl is not printing it. You can confirm this with netcat, for example. I commented out your Nothing case so that no matter what I typed in netcat I was sure to get the "In handleRequest!" message back:
server:
% ghc so.hs && ./so
Listening on port 3000...
Connected!
Nothing
client:
% nc localhost 3000
test ; My input, with a newline
In handleRequest! ; Printed out, no newline
Alternatively, you can use curl's -N option to disable buffering.
% curl -N localhost:3000
In handleRequest!

Azure media service job creation fails using rest api

Trying to consume Azure media service rest api. (following the tutorial : https://learn.microsoft.com/en-us/azure/media-services/media-services-rest-get-started)
Everything works fine until the point I try to create a Job. Sending the same request as in example (except asset id and token) and getting response :
Parsing request content failed due to: Make sure to only use property names that are defined by the type
Request:
POST https://wamsdubclus001rest-hs.cloudapp.net/api/Jobs HTTP/1.1
Connection: Keep-Alive
Content-Type: application/json
Accept: application/json; odata=verbose
Accept-Charset: UTF-8
Authorization: Bearer token -> here i send real token
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 3.0;NetFx
x-ms-version: 2.11
Content-Length: 458
Host: wamsdubclus001rest-hs.cloudapp.net
{
"Name":"TestJob",
"InputMediaAssets":[
{
"__metadata":{
"uri":"https://wamsdubclus001rest-hs.cloudapp.net/api/Assets('nb%3Acid%3AUUID%3A5168b52a-68ed-4df1-bac8-0648ce734ff6')"
}
}
],
"Tasks":[
{
"Configuration":"Adaptive Streaming",
"MediaProcessorId":"nb:mpid:UUID:ff4df607-d419-42f0-bc17-a481b1331e56",
"TaskBody":"<?xml version=\"1.0\" encoding=\"utf-8\"?><taskBody><inputAsset>JobInputAsset(0)</inputAsset> <outputAsset>JobOutputAsset(0)</outputAsset></taskBody>"
}
]
}
Response:
{
"error":{
"code":"",
"message":{
"lang":"en-US",
"value":"Parsing request content failed due to: Make sure to only use property names that are defined by the type"
}
}
}
It seems to be related with __metadata property. when I follow instruction from here : Creating Job from REST API returns a request property name error, the error changes:
"error":{
"code":"",
"message":{
"lang":"en-US",
"value":"Invalid input asset reference in TaskBody - "
}
}
}
Cant figure out whats wrong, thanks
Let me check on this, but it could be a couple issues that I have run into in the past.
First. Set both the Accept and Content-Type headers to:
"application/json; odata=verbose"
Next, double check that you are actually using the long underscore character on the metadata property. I've had issues where that was sending the wrong underscore character and it didn't match the property name.
Let me know if either of those helps.
It seems the issue was about "Content-Type". As I am using .net Core it was not easy to set the Conent-type as "application/json; odata=verbose".
1) Tried with RestSharp - dosnt support it, it cuts "odata=verbose" part out
2) Tried with Systsem.Net.Http.HttpClient -> Possible but difficult.
To add it as "Accept" :
MediaTypeWithQualityHeaderValue mtqhv;
MediaTypeWithQualityHeaderValue.TryParse("application/json;odata=verbose", out mtqhv);
client.DefaultRequestHeaders.Accept.Add(mtqhv);//ACCEPT header
To add it as "Content-Type" :
request.Content = new StringContent(content,
System.Text.Encoding.UTF8); //CONTENT-TYPE header -> default type will be text/html
request.Content.Headers.Clear(); // need to clear it - it will fail otherwise
request.Content.Headers.TryAddWithoutValidation("Content-Type","application/json;odata=verbose");

jobProgressToken: Jira (Zephyr) Rest API

I am using Jira (Zephyr) rest calls to create Test Cycle and add Tests cases into it. According to information mentioned here1 if I use this rest call to add tests to Cycle then as a response I will get JobProgressToken. JobProgressToken is nothing but will tell the progress of Test Case addition in Test Cycle.
Now the problem which I am facing is I am not getting any output from this JobProgressToken. I tried firing the GET rest call using the format mentioned but I am getting empty response.
Can somebody please explain how to use this JobProgressToken to get the Progress of my task?
I want to verify that the tests which I added to Cycle are added susccessfully or not?
Just ysterday I had the same issue. ZAPI documentation is really confusing. I found the following solution:
GET request for retrieving a job progress has the following form: http://jira/rest/zapi/latest/execution/jobProgress/0001498157843923-5056b64fdb-0001
where 0001498157843923-5056b64fdb-0001 is a value of the particular jobProgressToken
Right after getting a jobProgressToken as a result of some async operation, my code is waiting for the progress to became 1 (it is groing up from zero to 1)
//get the job progress token as a result of some async operation invocation
String jobProgressToken = new JSONObject(zapiResponse).getString("jobProgressToken");
waitAsyncJobToBeCompleted(jobProgressToken);
void waitAsyncJobToBeCompleted(String jobProgressToken) {
double jobProgress;
do {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
LOG.error("Error while try to make the thread sleeping 500ms. " + e.getLocalizedMessage());
e.printStackTrace();
}
jobProgress = getJobProgress(jobProgressToken);
} while (Double.compare(jobProgress, 1) <0);
private double getJobProgress(String jobProgressToken) {
URI uri = makeUriFromString(String.format(
GET_GetJobProgress, //Get request pattern
connectionParameters.getJiraUrl(),//host
jobProgressToken)); //parameters
HttpResponse response = executeHttpRequestWithResponse(new HttpGet(uri));
String zapiResponse = null;
try {
zapiResponse = EntityUtils.toString(response.getEntity());
LOG.trace("ZAPI RESPONSE: " + zapiResponse);
EntityUtils.consumeQuietly(response.getEntity()); //cleanup the HTTP response
double progress = new JSONObject(zapiResponse).getDouble("progress");
LOG.debug("Job progress: " + progress);
return progress;
} catch (IOException e) {
String err = String.format("Error while getting Zephyr API response: %s",
e.getLocalizedMessage());
LOG.fatal(err);
throw new RestApiException(err, e);
} catch (JSONException e) {
String err = String.format("Error while retrieving the job progress from JSON: %s\n%s",
zapiResponse, e.getLocalizedMessage());
LOG.fatal(err);
throw new RestApiException(err, e);
}
}
This is all magic :)
Following two logs: 1st for cloning test cycle, 2nd for deleting
ZephyrClient.invokeHttpPost - URI=http://jira/rest/zapi/latest/cycle JSON payload={"projectId": "13795","clonedCycleId": 2643,"name": "ZAPI client test","description": "Created With ZAPI client unit test","versionId": "-1"}
ZephyrClient.cloneTestCycle - RESPONSE JSON: {"jobProgressToken":"0001498218783350-5056b64fdb-0001"}
ZephyrClient.executeHttpRequestWithResponse - HTTP REQUEST: GET http://jira/rest/zapi/latest/execution/jobProgress/0001498218783350-5056b64fdb-0001 HTTP/1.1
ZephyrClient.executeHttpRequestWithResponse - HTTP RESPONSE: HTTP/1.1 200 OK
ZephyrClient.getJobProgress - ZAPI RESPONSE: {"timeTaken":"0 min, 1 sec","stepMessage":"","summaryMessage":"","errorMessage":"","progress":0.56,"message":"","stepLabel":"","stepMessages":[]}
ZephyrClient.getJobProgress - Job progress: 0.56
ZephyrClient.executeHttpRequestWithResponse - HTTP REQUEST: GET http://jira/rest/zapi/latest/execution/jobProgress/0001498218783350-5056b64fdb-0001 HTTP/1.1
ZephyrClient.executeHttpRequestWithResponse - HTTP RESPONSE: HTTP/1.1 200 OK
ZephyrClient.getJobProgress - ZAPI RESPONSE: {"timeTaken":"0 min, 1 sec","stepMessage":"","summaryMessage":"","errorMessage":"","progress":0.98,"message":"","stepLabel":"","stepMessages":[]}
ZephyrClient.getJobProgress - Job progress: 0.98
ZephyrClient.executeHttpRequestWithResponse - HTTP REQUEST: GET http://jira/rest/zapi/latest/execution/jobProgress/0001498218783350-5056b64fdb-0001 HTTP/1.1
ZephyrClient.executeHttpRequestWithResponse - HTTP RESPONSE: HTTP/1.1 200 OK
ZephyrClient.getJobProgress - ZAPI RESPONSE: {"timeTaken":"0 min, 2 sec","stepMessage":"","summaryMessage":"","errorMessage":"","progress":0.98,"message":"","stepLabel":"","stepMessages":[]}
ZephyrClient.getJobProgress - Job progress: 0.98
ZephyrClient.executeHttpRequestWithResponse - HTTP REQUEST: GET http://jira/rest/zapi/latest/execution/jobProgress/0001498218783350-5056b64fdb-0001 HTTP/1.1
ZephyrClient.executeHttpRequestWithResponse - HTTP RESPONSE: HTTP/1.1 200 OK
ZephyrClient.getJobProgress - ZAPI RESPONSE: {"timeTaken":"0 min, 3 sec","stepMessage":"","summaryMessage":"","errorMessage":"","progress":1.0,"message":"Cycle ZAPI client test created successfully.","stepLabel":"","stepMessages":[]}
ZephyrClient.getJobProgress - Job progress: 1.0
ZephyrClient.executeHttpRequestWithResponse - HTTP REQUEST: GET http://jira/rest/zapi/latest/cycle?projectId=13795&versionId=-1 HTTP/1.1
ZephyrClient.executeHttpRequestWithResponse - HTTP RESPONSE: HTTP/1.1 200 OK
ZephyrClient.invokeHttpDelete - URI=http://jira/rest/zapi/latest/cycle/2727
ZephyrClient.deleteTestCycle - RESPONSE JSON: {"jobProgressToken":"0001498218815183-5056b64fdb-0001"}
ZephyrClient.executeHttpRequestWithResponse - HTTP REQUEST: GET http://jira/rest/zapi/latest/execution/jobProgress/0001498218815183-5056b64fdb-0001 HTTP/1.1
ZephyrClient.executeHttpRequestWithResponse - HTTP RESPONSE: HTTP/1.1 200 OK
ZephyrClient.getJobProgress - ZAPI RESPONSE: {"timeTaken":"0 min, 0 sec","stepMessage":"","summaryMessage":"","errorMessage":"","progress":1.0,"message":"{\"success\":\"Cycle ZAPI client test успешно удален\"}","stepLabel":"","stepMessages":[]}
ZephyrClient.getJobProgress - Job progress: 1.0
ZephyrClient.executeHttpRequestWithResponse - HTTP REQUEST: GET http://jira/rest/zapi/latest/cycle?projectId=13795&versionId=-1 HTTP/1.1
ZephyrClient.executeHttpRequestWithResponse - HTTP RESPONSE: HTTP/1.1 200 OK
P.S. This is not about how it should be done.
This is about how does it work for me ;)

POST request on arduino with ESP8266 using WifiESP library

I am attempting to make RESTful POST request using the WifiESP library (https://github.com/bportaluri/WiFiEsp). I'm able to successfully make the request with curl, but consistently get an error using the Arduino and ESP. I suspect the problem is related to the manual formatting of the POST request the library requires, but I don't see anything wrong. Here my sanitized code:
if (client.connect(server, 80)) {
Serial.println("Connected to server");
// Make a HTTP request
String content = "{'JSON_key': 2.5}"; // some arbitrary JSON
client.println("POST /some/uri HTTP/1.1");
client.println("Host: http://things.ubidots.com");
client.println("Accept: */*");
client.println("Content-Length: " + sizeof(content));
client.println("Content-Type: application/json");
client.println();
client.println(content);
}
The error I get (via serial monitor) is this:
Connected to server
[WiFiEsp] Data packet send error (2)
[WiFiEsp] Failed to write to socket 3
[WiFiEsp] Disconnecting 3
My successful curl requests looks like this:
curl -X POST -H "Content-Type: application/json" -d 'Some JSON' http://things.ubidots.com/some/uri
After some experimentation, here is the solution to the multiple problems.
The JSON object was not correctly formatted. Single quotes were not accepted, so I needed to escape the double quotes.
The host does not need "http://" in a POST request; POST is a HTTP method.
The sizeof() method returns the size, in bytes, of the variable in memory rather than the length of the string. It needs to be replaced by .length().
Appending an integer to a string requires a cast.
This is the corrected code:
if (client.connect(server, 80)) {
Serial.println("Connected to server");
// Make the HTTP request
int value = 2.5; // an arbitrary value for testing
String content = "{\"JSON_key\": " + String(value) + "}";
client.println("POST /some/uri HTTP/1.1");
client.println("Host: things.ubidots.com");
client.println("Accept: */*");
client.println("Content-Length: " + String(content.length()));
client.println("Content-Type: application/json");
client.println();
client.println(content);
}
The code explained by Troy D is right and it's working .I think the error in posting the data to the server is due to this line
client.println("Content-Length: " + sizeof(content));
and the correct way is
client.println("Content-Length: " + String(content.length()));
Now coming to this error
Connected to server
[WiFiEsp] Data packet send error (2)
[WiFiEsp] Failed to write to socket 3
[WiFiEsp] Disconnecting 3
This is the error of library you can ignore it.
The problem with "Data packet send error (2)", "Failed to write to socket 3" and "Disconnecting 3" is not a problem within the WifiEsp library as far as I can see, believe it's more likely to be within the AT firmware. By default the http headers contain a "Connection: close" parameter which in normal cases should be correct. However with this bug the server will get disconnected before the reply is received on the client side and any response from the server will be identified as garbage data. Using the value "Connection: keep-alive" as a workaround will make it possible to receive the acceptance from the server in a proper way.
I'm running my Arduino + ESP8266-07 against a MVC based Web Api that I created on one of my servers and in the controllers Post-method I use a single string as return value, the value I return if everything is ok is simply one of the strings that WifiEsp keeps track of (It will still include the http status code in the response header that it returns)
public async Task<string> Post([FromBody]JObject payload)
{
//Code to handle the data received, in my case I log unit ip, macaddress, datetime and sensordata into a db with entity framework
return "SEND OK";
}
So in your Arduino code try following instead:
String PostHeader = "POST http://" + server + ":" + String(port) + "/api/values HTTP/1.1\r\n";
PostHeader += "Connection: keep-alive\r\n";
PostHeader += "Content-Type: application/json; charset=utf-8\r\n";
PostHeader += "Host: " + server + ":" + String(port) + "\r\n";
PostHeader += "Content-Length: " + String(jsonString.length()) + "\r\n\r\n";
PostHeader += jsonString;
client.connect(server.c_str(), port);
client.println(PostHeader);
client.stop();
In the file debug.h located in the library source code you could alter a define and get more output to your serial console. Open the file and change
#define _ESPLOGLEVEL_ 3
to
#define _ESPLOGLEVEL_ 4
Save the file and recompile/deploy your source code to your Arduino and you will get extensive information about all AT commands the library sends and what the library receives in return.