Esempio semplice di C di fare un POST HTTP e consumare la risposta

Mi piacerebbe creare un’applicazione C molto semplice che faccia un post HTTP. Ci vorranno alcuni parametri e usarli per build un URL. Mi piacerebbe fare un semplice HTTP POST e ottenere la risposta senza l’uso di arricciatura (le librerie non sono e non saranno installate sulla macchina che deve essere eseguita).

Pseudo-codice:

  1. Elabora 2 argomenti

  2. Inserisci gli argomenti nell’URL del modello: http://api.somesite.com/apikey=ARG1&command=ARG2

  3. Effettua il POST sull’URL generato

  4. Consuma la risposta

Le mie ricerche su Google e SO non hanno dato alcun risultato in merito.

Un messaggio ha una parte di intestazione e un corpo di messaggio separati da una riga vuota. La linea vuota è SEMPRE necessaria anche se non esiste un corpo del messaggio. L’intestazione inizia con un comando e ha linee aggiuntive di coppie di valori chiave separate da due punti e uno spazio. Se c’è un corpo del messaggio, può essere qualsiasi cosa tu voglia che sia.

Le righe nell’intestazione e la riga vuota alla fine dell’intestazione devono terminare con un ritorno carraige e una coppia linefeed (vedere lo stile di interruzione della riga di intestazione HTTP ), ecco perché le righe hanno \ r \ n alla fine.

Un URL ha la forma di http://host:port/path?query_string

Esistono due modi principali per inviare una richiesta a un sito Web:

  • GET: la stringa di query è facoltativa ma, se specificato, deve essere ragionevolmente breve. Per questo motivo l’intestazione potrebbe essere semplicemente il comando GET e nient’altro. Un messaggio di esempio potrebbe essere:

     GET /path?query_string HTTP/1.0\r\n \r\n 
  • POST: Ciò che normalmente si trova nella stringa della query è invece nel corpo del messaggio. Per questo motivo l’intestazione deve includere Content-Type: e Content-Length: attributes così come il comando POST. Un messaggio di esempio potrebbe essere:

     POST /path HTTP/1.0\r\n Content-Type: text/plain\r\n Content-Length: 12\r\n \r\n query_string 

Quindi, per rispondere alla tua domanda: se l’URL a cui sei interessato a POSTing è http://api.somesite.com/apikey=ARG1&command=ARG2 allora non c’è un corpo o una stringa di query e, di conseguenza, non c’è motivo di postare perché lì non è nulla da mettere nel corpo del messaggio e quindi nulla da inserire in Content-Type: and Content-Length:

Immagino che potresti POSARE se lo volessi davvero. In questo caso il tuo messaggio sarà simile a:

 POST /apikey=ARG1&command=ARG2 HTTP/1.0\r\n \r\n 

Quindi per inviare il messaggio il programma C deve:

  • creare un socket
  • cercare l’indirizzo IP
  • apri la presa
  • invia la richiesta
  • attendere la risposta
  • chiudere la presa

Le chiamate di invio e ricezione non necessariamente invieranno / riceveranno TUTTI i dati che fornirai, ma restituiranno il numero di byte effettivamente inviati / ricevuti. Spetta a te chiamarli in un ciclo e inviare / ricevere il resto del messaggio.

Quello che non ho fatto in questo esempio è una sorta di vero controllo degli errori – quando qualcosa non funziona, esco dal programma. Fammi sapere se funziona per te:

 #include  /* printf, sprintf */ #include  /* exit */ #include  /* read, write, close */ #include  /* memcpy, memset */ #include  /* socket, connect */ #include  /* struct sockaddr_in, struct sockaddr */ #include  /* struct hostent, gethostbyname */ void error(const char *msg) { perror(msg); exit(0); } int main(int argc,char *argv[]) { /* first what are we going to send and where are we going to send it? */ int portno = 80; char *host = "api.somesite.com"; char *message_fmt = "POST /apikey=%s&command=%s HTTP/1.0\r\n\r\n"; struct hostent *server; struct sockaddr_in serv_addr; int sockfd, bytes, sent, received, total; char message[1024],response[4096]; if (argc < 3) { puts("Parameters:  "); exit(0); } /* fill in the parameters */ sprintf(message,message_fmt,argv[1],argv[2]); printf("Request:\n%s\n",message); /* create the socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); /* lookup the ip address */ server = gethostbyname(host); if (server == NULL) error("ERROR, no such host"); /* fill in the structure */ memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); /* connect the socket */ if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) error("ERROR connecting"); /* send the request */ total = strlen(message); sent = 0; do { bytes = write(sockfd,message+sent,total-sent); if (bytes < 0) error("ERROR writing message to socket"); if (bytes == 0) break; sent+=bytes; } while (sent < total); /* receive the response */ memset(response,0,sizeof(response)); total = sizeof(response)-1; received = 0; do { bytes = read(sockfd,response+received,total-received); if (bytes < 0) error("ERROR reading response from socket"); if (bytes == 0) break; received+=bytes; } while (received < total); if (received == total) error("ERROR storing complete response from socket"); /* close the socket */ close(sockfd); /* process response */ printf("Response:\n%s\n",response); return 0; } 

Come l'altra risposta ha sottolineato, 4096 byte non è una risposta molto grande. Ho preso quel numero a caso assumendo che la risposta alla tua richiesta sarebbe stata breve. Se può essere grande hai due scelte:

  • leggere Content-Length: intestazione dalla risposta e quindi allocare dynamicmente memoria sufficiente a contenere l'intera risposta.
  • scrivi la risposta a un file mentre arrivano i pezzi

Ulteriori informazioni per rispondere alla domanda posta nei commenti:

Cosa succede se si desidera inserire i dati POST nel corpo del messaggio? Quindi è necessario includere il Content-Type: e Content-Length: headers. Lunghezza contenuto: è la lunghezza effettiva di tutto dopo la riga vuota che separa l'intestazione dal corpo.

Ecco un esempio che accetta i seguenti argomenti della riga di comando:

  • ospite
  • porta
  • comando (GET o POST)
  • percorso (non includendo i dati della query)
  • dati di query (inseriti nella stringa di query per GET e nel corpo per POST)
  • lista di intestazioni (Content-Length: è automatico se si utilizza il POST)

Quindi, per la domanda originale correrai:

 a.out api.somesite.com 80 GET "/apikey=ARG1&command=ARG2" 

E per la domanda posta nei commenti correrai:

 a.out api.somesite.com 80 POST / "name=ARG1&value=ARG2" "Content-Type: application/x-www-form-urlencoded" 

Ecco il codice:

 #include  /* printf, sprintf */ #include  /* exit, atoi, malloc, free */ #include  /* read, write, close */ #include  /* memcpy, memset */ #include  /* socket, connect */ #include  /* struct sockaddr_in, struct sockaddr */ #include  /* struct hostent, gethostbyname */ void error(const char *msg) { perror(msg); exit(0); } int main(int argc,char *argv[]) { int i; /* first where are we going to send it? */ int portno = atoi(argv[2])>0?atoi(argv[2]):80; char *host = strlen(argv[1])>0?argv[1]:"localhost"; struct hostent *server; struct sockaddr_in serv_addr; int sockfd, bytes, sent, received, total, message_size; char *message, response[4096]; if (argc < 5) { puts("Parameters:     [ []]"); exit(0); } /* How big is the message? */ message_size=0; if(!strcmp(argv[3],"GET")) { message_size+=strlen("%s %s%s%s HTTP/1.0\r\n"); /* method */ message_size+=strlen(argv[3]); /* path */ message_size+=strlen(argv[4]); /* headers */ if(argc>5) message_size+=strlen(argv[5]); /* query string */ for(i=6;i5) message_size+=strlen("Content-Length: %d\r\n")+10; /* content length */ message_size+=strlen("\r\n"); /* blank line */ if(argc>5) message_size+=strlen(argv[5]); /* body */ } /* allocate space for the message */ message=malloc(message_size); /* fill in the parameters */ if(!strcmp(argv[3],"GET")) { if(argc>5) sprintf(message,"%s %s%s%s HTTP/1.0\r\n", strlen(argv[3])>0?argv[3]:"GET", /* method */ strlen(argv[4])>0?argv[4]:"/", /* path */ strlen(argv[5])>0?"?":"", /* ? */ strlen(argv[5])>0?argv[5]:""); /* query string */ else sprintf(message,"%s %s HTTP/1.0\r\n", strlen(argv[3])>0?argv[3]:"GET", /* method */ strlen(argv[4])>0?argv[4]:"/"); /* path */ for(i=6;i0?argv[3]:"POST", /* method */ strlen(argv[4])>0?argv[4]:"/"); /* path */ for(i=6;i5) sprintf(message+strlen(message),"Content-Length: %d\r\n",strlen(argv[5])); strcat(message,"\r\n"); /* blank line */ if(argc>5) strcat(message,argv[5]); /* body */ } /* What are we going to send? */ printf("Request:\n%s\n",message); /* create the socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); /* lookup the ip address */ server = gethostbyname(host); if (server == NULL) error("ERROR, no such host"); /* fill in the structure */ memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); /* connect the socket */ if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) error("ERROR connecting"); /* send the request */ total = strlen(message); sent = 0; do { bytes = write(sockfd,message+sent,total-sent); if (bytes < 0) error("ERROR writing message to socket"); if (bytes == 0) break; sent+=bytes; } while (sent < total); /* receive the response */ memset(response,0,sizeof(response)); total = sizeof(response)-1; received = 0; do { bytes = read(sockfd,response+received,total-received); if (bytes < 0) error("ERROR reading response from socket"); if (bytes == 0) break; received+=bytes; } while (received < total); if (received == total) error("ERROR storing complete response from socket"); /* close the socket */ close(sockfd); /* process response */ printf("Response:\n%s\n",response); free(message); return 0; } 

La risposta di Jerry è grandiosa. Tuttavia, non gestisce le risposte di grandi dimensioni. Un semplice cambiamento per gestire questo:

 memset(response, 0, sizeof(response)); total = sizeof(response)-1; received = 0; do { printf("RESPONSE: %s\n", response); // HANDLE RESPONSE CHUCK HERE BY, FOR EXAMPLE, SAVING TO A FILE. memset(response, 0, sizeof(response)); bytes = recv(sockfd, response, 1024, 0); if (bytes < 0) printf("ERROR reading response from socket"); if (bytes == 0) break; received+=bytes; } while (1); 

Dopo settimane di ricerca. Ho trovato il seguente codice. Credo che questo sia il minimo indispensabile per stabilire una connessione sicura con SSL a un server web.

 #include  #include  #include  #include  #define APIKEY "YOUR_API_KEY" #define HOST "YOUR_WEB_SERVER_URI" #define PORT "443" int main() { // // Initialize the variables // BIO* bio; SSL* ssl; SSL_CTX* ctx; // // Registers the SSL/TLS ciphers and digests. // // Basically start the security layer. // SSL_library_init(); // // Creates a new SSL_CTX object as a framework to establish TLS/SSL // or DTLS enabled connections // ctx = SSL_CTX_new(SSLv23_client_method()); // // -> Error check // if (ctx == NULL) { printf("Ctx is null\n"); } // // Creates a new BIO chain consisting of an SSL BIO // bio = BIO_new_ssl_connect(ctx); // // Use the variable from the beginning of the file to create a // string that contains the URL to the site that you want to connect // to while also specifying the port. // BIO_set_conn_hostname(bio, HOST ":" PORT); // // Attempts to connect the supplied BIO // if(BIO_do_connect(bio) <= 0) { printf("Failed connection\n"); return 1; } else { printf("Connected\n"); } // // The bare minimum to make a HTTP request. // char* write_buf = "POST / HTTP/1.1\r\n" "Host: " HOST "\r\n" "Authorization: Basic " APIKEY "\r\n" "Connection: close\r\n" "\r\n"; // // Attempts to write len bytes from buf to BIO // if(BIO_write(bio, write_buf, strlen(write_buf)) <= 0) { // // Handle failed writes here // if(!BIO_should_retry(bio)) { // Not worth implementing, but worth knowing. } // // -> Let us know about the failed writes // printf("Failed write\n"); } // // Variables used to read the response from the server // int size; char buf[1024]; // // Read the response message // for(;;) { // // Get chunks of the response 1023 at the time. // size = BIO_read(bio, buf, 1023); // // If no more data, then exit the loop // if(size <= 0) { break; } // // Terminate the string with a 0, to let know C when the string // ends. // buf[size] = 0; // // -> Print out the response // printf("%s", buf); } // // Clean after ourselves // BIO_free_all(bio); SSL_CTX_free(ctx); return 0; } 

Il codice sopra spiegherà in dettaglio come stabilire una connessione TLS con un server remoto.

Nota importante : questo codice non controlla se la chiave pubblica è stata firmata da un’autorità valida. Significato Non uso i certificati radice per la convalida. Non dimenticare di implementare questo controllo altrimenti non saprai se stai collegando il sito giusto

Quando si tratta della richiesta stessa. Non è altro che scrivere la richiesta HTTP a mano.

È inoltre ansible trovare sotto questo collegamento una spiegazione su come installare openSSL nel proprio sistema e su come compilare il codice in modo che utilizzi la libreria sicura .

Manico aggiunto
Aggiunta intestazione host.
Aggiunto il supporto per linux / windows, testato (XP, WIN7).
ATTENZIONE: ERRORE: “errore di segmentazione” se nessun host, percorso o porta come argomento.

 #include  /* printf, sprintf */ #include  /* exit, atoi, malloc, free */ #include  /* read, write, close */ #include  /* memcpy, memset */ #ifdef __linux__ #include  /* socket, connect */ #include  /* struct hostent, gethostbyname */ #include  /* struct sockaddr_in, struct sockaddr */ #elif _WIN32 #include  #include  #include  #pragma comment(lib,"ws2_32.lib") //Winsock Library #else #endif void error(const char *msg) { perror(msg); exit(0); } int main(int argc,char *argv[]) { int i; struct hostent *server; struct sockaddr_in serv_addr; int bytes, sent, received, total, message_size; char *message, response[4096]; int portno = atoi(argv[2])>0?atoi(argv[2]):80; char *host = strlen(argv[1])>0?argv[1]:"localhost"; char *path = strlen(argv[4])>0?argv[4]:"/"; if (argc < 5) { puts("Parameters:     [ []]"); exit(0); } /* How big is the message? */ message_size=0; if(!strcmp(argv[3],"GET")) { printf("Process 1\n"); message_size+=strlen("%s %s%s%s HTTP/1.0\r\nHost: %s\r\n"); /* method */ message_size+=strlen(argv[3]); /* path */ message_size+=strlen(path); /* headers */ if(argc>5) message_size+=strlen(argv[5]); /* query string */ for(i=6;i5) message_size+=strlen("Content-Length: %d\r\n")+10; /* content length */ message_size+=strlen("\r\n"); /* blank line */ if(argc>5) message_size+=strlen(argv[5]); /* body */ } printf("Allocating...\n"); /* allocate space for the message */ message=malloc(message_size); /* fill in the parameters */ if(!strcmp(argv[3],"GET")) { if(argc>5) sprintf(message,"%s %s%s%s HTTP/1.0\r\nHost: %s\r\n", strlen(argv[3])>0?argv[3]:"GET", /* method */ path, /* path */ strlen(argv[5])>0?"?":"", /* ? */ strlen(argv[5])>0?argv[5]:"",host); /* query string */ else sprintf(message,"%s %s HTTP/1.0\r\nHost: %s\r\n", strlen(argv[3])>0?argv[3]:"GET", /* method */ path,host); /* path */ for(i=6;i0?argv[3]:"POST", /* method */ path,host); /* path */ for(i=6;i5) sprintf(message+strlen(message),"Content-Length: %d\r\n",(int)strlen(argv[5])); strcat(message,"\r\n"); /* blank line */ if(argc>5) strcat(message,argv[5]); /* body */ } printf("Processed\n"); /* What are we going to send? */ printf("Request:\n%s\n",message); /* lookup the ip address */ total = strlen(message); /* create the socket */ #ifdef _WIN32 WSADATA wsa; SOCKET s; printf("\nInitialising Winsock..."); if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) { printf("Failed. Error Code : %d",WSAGetLastError()); return 1; } printf("Initialised.\n"); //Create a socket if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET) { printf("Could not create socket : %d" , WSAGetLastError()); } printf("Socket created.\n"); server = gethostbyname(host); serv_addr.sin_addr.s_addr = inet_addr(server->h_addr); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); //Connect to remote server if (connect(s , (struct sockaddr *)&serv_addr , sizeof(serv_addr)) < 0) { printf("connect failed with error code : %d" , WSAGetLastError()); return 1; } puts("Connected"); if( send(s , message , strlen(message) , 0) < 0) { printf("Send failed with error code : %d" , WSAGetLastError()); return 1; } puts("Data Send\n"); //Receive a reply from the server if((received = recv(s , response , 2000 , 0)) == SOCKET_ERROR) { printf("recv failed with error code : %d" , WSAGetLastError()); } puts("Reply received\n"); //Add a NULL terminating character to make it a proper string before printing response[received] = '\0'; puts(response); closesocket(s); WSACleanup(); #endif #ifdef __linux__ int sockfd; server = gethostbyname(host); if (server == NULL) error("ERROR, no such host"); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); /* fill in the structure */ memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); /* connect the socket */ if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) error("ERROR connecting"); /* send the request */ sent = 0; do { bytes = write(sockfd,message+sent,total-sent); if (bytes < 0) error("ERROR writing message to socket"); if (bytes == 0) break; sent+=bytes; } while (sent < total); /* receive the response */ memset(response, 0, sizeof(response)); total = sizeof(response)-1; received = 0; printf("Response: \n"); do { printf("%s", response); memset(response, 0, sizeof(response)); bytes = recv(sockfd, response, 1024, 0); if (bytes < 0) printf("ERROR reading response from socket"); if (bytes == 0) break; received+=bytes; } while (1); if (received == total) error("ERROR storing complete response from socket"); /* close the socket */ close(sockfd); #endif free(message); return 0; }