what's all the headers needed in a put request

Hi.

I'm facing a issue using and http server on arduino and an ionic app client.

I'm using this http server: NitrofMtl/uHTTP.

It was running well until I start to use an ionic3 app. Before I was using angular.js(1) on a web page and I did not have any error message. Now with the app, Put request did not work, on device or with $ionic run, it just dont work, and on $ionic serve, with a cors plug-in I get this error:

HttpErrorResponse {headers: HttpHeaders, status: 200, statusText: "OK", url: "http://192.168.0.110/switch", ok: false, …}
error
:
{error: SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) at XMLHttpRequest.onLo…, text: ""}
headers
:
HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, headers: Map(0)}
message
:
"Http failure during parsing for http://192.168.0.110/switch"
name
:
"HttpErrorResponse"
ok
:
false
status
:
200
statusText
:
"OK"

But the arduino side get the request and data, like it is kind of only do not close the request well so the subscriber return error.

Here the function that handle put request on server:

if (*response = available()) {

    bool requestFound = false;
    uint8_t thisMethod = method();
    //Serial.println("receive request");
    switch (thisMethod) {
  case uHTTP_METHOD_PUT:
      //Serial.print("put request= "); Serial.println(this->uri(1));
        for(int i=0; i<sizePutContainer; i++) {
          if(strcmp(this->uri(1), container_Put[i].id) == 0){
            container_Put[i].callback();                       //<--- the callback just parse the request body
            response->println("HTTP/1.0 200 OK\r\n");
            requestFound = true;
            break;
          }
if(!requestFound)  render(404, TEXT_HTML); //if request not exist, send error page
        break; /*...
        }
 response->stop();

Is the put request should send other header to confirm the request reception...

Post complete code, the error is probably not the tiny excerpt you posted!

If you read the error text correctly you can see that your server don't return valid JSON (at least that unlinked "ionic" anything is thinking that, maybe that part is wrong).

Like I refer, the complete uHTTP file is on the github link I provide. all that is on the .ino file is the handler() ant the body parsing.

Still, this is the project where it used:
multi_room_thermostat

In the void lood(), the function werbServ() is call, it is located in the webCmd.ino file with all the json object function.

Like I refer, the complete uHTTP file is on the github link I provide. all that is on the .ino file is the handler() ant the body parsing.

I repeat: post complete code, we don't go and patch several parts together from different sources just to hear afterwards that the OP did it otherwise. You're asking for help so provide the information we ask for.

On what type of Arduino is that code running? A Due?

The problem is probably that your µHTTP code doesn't return a complete header in the PUT method case, just the first line of the header. And given the error message you posted your "ionic" thing seems to expect a JSON response in every case so it might not be lucky with an empty 200 response. As you still didn't told us what that "ionic" thing is (link?) this is just a wild guess.

ho yes I forgot the ionic app.

this is the function that I came with this error, in ../../providers/home-provider/home.ts file:

switchChannel(cb: Channel) {
    var chanObj = {
      switchCh: {
        canal: cb.canal,
        status: cb.status
      }
    };
    this.http.put(this.server.url() + '/switch', chanObj).subscribe(result => console.log('http put switch succesfull'),
    error => {
         console.error(error)
    });
  }

The problem is probably that your µHTTP code doesn't return a complete header in the PUT method case, just the first line of the header.

That's i was thinking.. but I don't know how to...

ho yes I forgot the ionic app.

So this is your own development?

I don't know the Cordova environment well enough to predict the reaction on incomplete headers but I guess the problem from my last answer might solve the error. And to serve the framework with an answer I would at least return a very simple JSON doucment ("{}").

thanks... I'll try that. also, if it is not working, i'll try to get more transaction text for server when passing a request to investigate if there's a difference between loading from local page and from ionic3...

Ok some digging, I found that ionic request is block on request method: OPTION. I add lot of debugging statement in my server.

If I make a request with the web page, it does the put request immediately. The Ionic app ask for method OPTION before, and I believe that I don't send back the good answer after because the put request is never send to the server after.

here the handler of request on the server:

 EthernetClient uHTTP::available(){
  EthernetClient client;

  memset(__uri, 0, sizeof(__uri));
  memset(__query, 0, sizeof(__query));
  memset(__body, 0, sizeof(__body));
  memset(&__head, 0, sizeof(__head));

  if(client = EthernetServer::available()){
    uint16_t cursor = 0, cr = 0;
    char buffer[uHTTP_BUFFER_SIZE] = {0};
    bool sub = false;

    enum state_t {METHOD, URI, QUERY, PROTO, KEY, VALUE, BODY};
    state_t state = METHOD;

    enum header_t {START, AUTHORIZATION, CONTENT_TYPE, CONTENT_LENGTH, ORIGIN};
    header_t header = START;

    uHTTP_PRINTLN();
    uHTTP_PRINTLN();
    uHTTP_PRINTLN();    
 
    while(client.connected() && client.available()){
      char c = client.read();

      if(c == '/r') uHTTP_PRINTLN();
      uHTTP_PRINT(c);

      (c == '\r' || c == '\n') ? cr++ : cr = 0;
 
      switch(state){
       case METHOD:
       if(c == ' '){
        if(strncmp(buffer, PSTR("OP"), 2) == 0) __method = uHTTP_METHOD_OPTIONS;
        else if(strncmp(buffer, PSTR("HE"), 2) == 0) __method = uHTTP_METHOD_HEAD;
        else if(strncmp(buffer, PSTR("PO"), 2) == 0) __method = uHTTP_METHOD_POST;
        else if(strncmp(buffer, PSTR("PU"), 2) == 0) __method = uHTTP_METHOD_PUT;
        else if(strncmp(buffer, PSTR("PA"), 2) == 0) __method = uHTTP_METHOD_PATCH;
        else if(strncmp(buffer, PSTR("DE"), 2) == 0) __method = uHTTP_METHOD_DELETE;
        else if(strncmp(buffer, PSTR("TR"), 2) == 0) __method = uHTTP_METHOD_TRACE;
        else if(strncmp(buffer, PSTR("CO"), 2) == 0) __method = uHTTP_METHOD_CONNECT;
        else __method = uHTTP_METHOD_GET;
        state = URI; 
        cursor = 0; 
      }else if(cursor < uHTTP_METHOD_SIZE - 1){
       buffer[cursor++] = c; 
       buffer[cursor] = '\0'; 
     }
     break;
     case URI:
     if(c == ' '){
       state = PROTO;
       cursor = 0;
     }else if(c == '?'){
       state = QUERY;
       cursor = 0;
     }else if(cursor < uHTTP_URI_SIZE - 1){
       __uri[cursor++] = c;
       __uri[cursor] = '\0';
     }
     break;
     case QUERY:
     if(c == ' '){
       state = PROTO;
       cursor = 0;
     }else if(cursor < uHTTP_QUERY_SIZE - 1){
       __query[cursor++] = c;
       __query[cursor] = '\0';
     }
     break;
     case PROTO:
     if(cr == 2){ state = KEY; cursor = 0; }
     break;
     case KEY:
     if (cr == 4){ state = BODY; cursor = 0; }
     else if(c == ' '){ state = VALUE; cursor = 0; }
     else if(c != ':' && cursor < uHTTP_BUFFER_SIZE){ buffer[cursor++] = c; buffer[cursor] = '\0'; }
     break;
     case VALUE:
     if(cr == 2){
       switch(header){
         case AUTHORIZATION:
         strncpy(__head.auth, buffer, uHTTP_AUTH_SIZE);
         break;
         case CONTENT_TYPE:
         strncpy(__head.type, buffer, uHTTP_TYPE_SIZE);
         break;
         case ORIGIN:
         strncpy(__head.orig, buffer, uHTTP_ORIG_SIZE);
         break;
         case CONTENT_LENGTH:
         __head.length = atoi(buffer);
         break;
         break;
       }
       state = KEY; header = START; cursor = 0; sub = false;
     }else if(c != '\r' && c!= '\n'){
       if(header == START){
         if(strncmp(buffer, PSTR("Auth"), 4) == 0) header = AUTHORIZATION;
         else if(strncmp(buffer, PSTR("Content-T"), 9) == 0) header = CONTENT_TYPE;
         else if(strncmp(buffer, PSTR("Content-L"), 9) == 0) header = CONTENT_LENGTH;
       }

              			// Fill buffer
       if(cursor < uHTTP_BUFFER_SIZE - 1){
         switch(header){
          case AUTHORIZATION:
          if(sub){ buffer[cursor++] = c; buffer[cursor] = '\0'; }
          else if(c == ' ') sub = true;
          break;
          case CONTENT_TYPE:
          case CONTENT_LENGTH:
          buffer[cursor++] = c; buffer[cursor] = '\0';
          break;
        }
      }
    }
    break;
    case BODY:
    if(cr == 2 || __head.length == 0) client.flush();
    else if(cursor < uHTTP_BODY_SIZE - 1){ __body[cursor++] = c; __body[cursor] = '\0'; }
    break;
  }
}
}
return client;

void uHTTP::send_headers(uint16_t code){
  Serial.println("send headers");
  header_t head = __head;

  uHTTP_PRINT("HTTP/1.1 ");
  response->print("HTTP/1.1 ");
  switch(code){
    case 200:
    uHTTP_PRINTLN("200 OK");
    uHTTP_PRINTLN("Content-Type: application/json");
    uHTTP_PRINTLN("Access-Control-Allow-Origin: *");
    response->println("200 OK");
    response->println("Content-Type: application/json");
    response->println("Access-Control-Allow-Origin: *");
    if(strlen(head.orig)){
      //response->print("Access-Control-Allow-Origin: ");//to alllow CORS, allow origin *
      //response->println(head.orig);                    //to alllow CORS, allow origin *
      uHTTP_PRINTLN("Access-Control-Allow-Origin: *");
      uHTTP_PRINTLN("Access-Control-Allow-Methods: GET,PUT,HEAD");
      uHTTP_PRINTLN("Access-Control-Allow-Headers: Authorization, Content-Type");
      uHTTP_PRINTLN("Access-Control-Allow-Credentials: true");
      uHTTP_PRINTLN("Access-Control-Max-Age: 1000");
      response->println("Access-Control-Allow-Origin: *");
      response->println("Access-Control-Allow-Methods: GET,PUT,HEAD");
      response->println("Access-Control-Allow-Headers: Authorization, Content-Type");
      response->println("Access-Control-Allow-Credentials: true");
      response->println("Access-Control-Max-Age: 1000");
    }
    break;
    case 204:
    uHTTP_PRINTLN("204 OK");
    uHTTP_PRINTLN("100 continue");
    response->println("204 OK");
    response->println("100 continue");
    break;
  }
}
}

Then here the request send by the Ionic app:

OPTIONS /switch HTTP/1.1
Host: 107.171.185.29:8081
Connection: keep-alive
Access-Control-Request-Method: PUT
Origin: http://192.168.0.101:8100
User-Agent: Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-4; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/65.0.3325.109 Mobile Safari/537.36
Access-Control-Request-Headers: content-type
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: fr-CA,en-US;q=0.9
X-Requested-With: io.ionic.starter


       receive request
URI: switch
OPTION request= switch
send headers
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: *

I think that my void uHTTP::send_headers(uint16_t code) function do not return all line needed... maybe related to CORS also...

It probably just asks for the allowed methods on the URL. Try to implement the OPTIONS method by just returning an Allow header line:

Allow: GET,PUT,OPTIONS

You're right.

In send_header,

if(strlen(head.orig))

was false in option, I move Allow: GET, PUT,HEAD outside and now it works.

This part of the library is not my work, I don't know what header_t object are, I dont fin any reference to it on the web... Don't know from where it is define...

but at least, it work. Thanks.

Regards. Nitrof

[EDIT]After reanalysis, error still occur in ionic promise, but not at the same place, the request made it way to the server and is processed, but promise still return error. I will have to dig a little more.

Also, I found that there is 2 library HTTP, one to be native ionic... now I use common/angular (default in ionic) I'll give a try to native... but it is, for now , a bit outside of arduino topic...

[Final EDIT]Finally. Got it to work it right. It was the new angular http that have more exigence with header response. It probably have some correction to do, but at least, client do not get an error anymore.

The problem was cause by 2 thing: First, because the app cause an CORS issue, the server could not give an "204 NO CONTENT" response.

So, second, the default format body of a "200 OK" response is JSON, so empty body cause the parse error.

As suggest by pylon, I just posted a empty JSON object, But the first time I've tried it, I forgot an blank line.. :stuck_out_tongue:

So this is the final response to client:

      case uHTTP_METHOD_PUT:
        send_headers(200); ///=>print to client:
/* 
   uHTTP_PRINTLN("200 OK");
    uHTTP_PRINTLN("Content-Type: application/json");
    uHTTP_PRINTLN("Access-Control-Allow-Methods: GET, PUT, HEAD");
    //if(strlen(head.orig)){
      uHTTP_PRINTLN("Access-Control-Allow-Origin: *");
      //uHTTP_PRINTLN("Access-Control-Allow-Methods: GET,PUT,HEAD");
      uHTTP_PRINTLN("Access-Control-Allow-Headers: Authorization, Content-Type");
      uHTTP_PRINTLN("Access-Control-Allow-Credentials: true");
      uHTTP_PRINTLN("Access-Control-Max-Age: 1000");

*/
//-- the callback parse JSON object

uHTTP_PRINTLN();uHTTP_PRINTLN("{}");   //<==== sending empty body
  
            break;