ข้อความมีส่วนหัวและเนื้อหาของข้อความคั่นด้วยบรรทัดว่าง จำเป็นต้องใช้บรรทัดว่างเสมอแม้ว่าจะไม่มีเนื้อหาของข้อความก็ตาม ส่วนหัวเริ่มต้นด้วยคำสั่งและมีบรรทัดเพิ่มเติมของคู่คีย์ค่าที่คั่นด้วยเครื่องหมายจุดคู่และช่องว่าง หากมีเนื้อหาข้อความอาจเป็นอะไรก็ได้ที่คุณต้องการ
เส้นในส่วนหัวและบรรทัดว่างที่ส่วนท้ายของส่วนหัวจะต้องลงท้ายด้วยคู่ผลตอบแทน carraige และ linefeed (ดูลักษณะการแบ่งบรรทัดส่วนหัว HTTP ) ดังนั้นจึงเป็นสาเหตุที่บรรทัดเหล่านั้นมี \ r \ n ที่ส่วนท้าย
URL มีรูปแบบของ http://host:port/path?query_string
มีสองวิธีหลักในการส่งคำขอไปยังเว็บไซต์:
GET: สตริงการสืบค้นเป็นทางเลือก แต่หากระบุไว้ต้องสั้นพอสมควร ด้วยเหตุนี้ส่วนหัวอาจเป็นเพียงคำสั่ง GET และไม่มีอะไรอื่น ข้อความตัวอย่างอาจเป็น:
GET /path?query_string HTTP/1.0\r\n
\r\n
โพสต์: สิ่งที่ปกติจะอยู่ในสตริงข้อความค้นหาจะอยู่ในเนื้อความของข้อความแทน ด้วยเหตุนี้ส่วนหัวจึงต้องมีแอตทริบิวต์ Content-Type: และ Content-Length: รวมทั้งคำสั่ง POST ข้อความตัวอย่างอาจเป็น:
POST /path HTTP/1.0\r\n
Content-Type: text/plain\r\n
Content-Length: 12\r\n
\r\n
query_string
ดังนั้นเพื่อตอบคำถามของคุณ: หาก URL ที่คุณสนใจในการโพสต์คือhttp://api.somesite.com/apikey=ARG1&command=ARG2 แสดงว่าไม่มีเนื้อความหรือสตริงการค้นหาดังนั้นจึงไม่มีเหตุผลที่จะโพสต์เนื่องจากมี ไม่มีอะไรที่จะใส่ไว้ในเนื้อหาของข้อความและไม่มีอะไรให้ใส่ใน Content-Type: และ Content-Length:
ฉันเดาว่าคุณสามารถโพสต์ได้ถ้าคุณต้องการจริงๆ ในกรณีนี้ข้อความของคุณจะมีลักษณะดังนี้:
POST /apikey=ARG1&command=ARG2 HTTP/1.0\r\n
\r\n
ดังนั้นในการส่งข้อความโปรแกรม C ต้องการ:
- สร้างซ็อกเก็ต
- ค้นหาที่อยู่ IP
- เปิดซ็อกเก็ต
- ส่งคำขอ
- รอการตอบกลับ
- ปิดซ็อกเก็ต
การส่งและรับสายไม่จำเป็นต้องส่ง / รับข้อมูลทั้งหมดที่คุณให้มา แต่จะส่งคืนจำนวนไบต์ที่รับ / ส่งจริง ขึ้นอยู่กับคุณที่จะเรียกพวกเขาแบบวนซ้ำและส่ง / รับส่วนที่เหลือของข้อความ
สิ่งที่ฉันไม่ได้ทำในตัวอย่างนี้คือการตรวจสอบข้อผิดพลาดจริงใด ๆ - เมื่อมีบางอย่างล้มเหลวฉันเพียงแค่ออกจากโปรแกรม แจ้งให้เราทราบหากเหมาะสำหรับคุณ:
#include <stdio.h> /* printf, sprintf */
#include <stdlib.h> /* exit */
#include <unistd.h> /* read, write, close */
#include <string.h> /* memcpy, memset */
#include <sys/socket.h> /* socket, connect */
#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */
#include <netdb.h> /* struct hostent, gethostbyname */
void error(const char *msg) { perror(msg); exit(0); }
int main(int argc,char *argv[])
{
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: <apikey> <command>"); exit(0); }
sprintf(message,message_fmt,argv[1],argv[2]);
printf("Request:\n%s\n",message);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) error("ERROR opening socket");
server = gethostbyname(host);
if (server == NULL) error("ERROR, no such host");
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);
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
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);
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(sockfd);
printf("Response:\n%s\n",response);
return 0;
}
เช่นเดียวกับคำตอบอื่น ๆ ที่ระบุไว้ 4096 ไบต์ไม่ใช่คำตอบที่ใหญ่มาก ฉันเลือกหมายเลขนั้นโดยสุ่มโดยสมมติว่าการตอบกลับคำขอของคุณจะสั้น ถ้ามันใหญ่คุณมีสองทางเลือก:
- อ่านส่วนหัว Content-Length: จากการตอบกลับจากนั้นจัดสรรหน่วยความจำให้เพียงพอแบบไดนามิกเพื่อรองรับการตอบสนองทั้งหมด
- เขียนการตอบกลับไปยังไฟล์เมื่อชิ้นส่วนมาถึง
ข้อมูลเพิ่มเติมเพื่อตอบคำถามที่ถามในความคิดเห็น:
จะเกิดอะไรขึ้นหากคุณต้องการโพสต์ข้อมูลในเนื้อหาของข้อความ จากนั้นคุณจะต้องรวมส่วนหัว Content-Type: และ Content-Length: ความยาวของเนื้อหา: คือความยาวจริงของทุกสิ่งหลังบรรทัดว่างที่แยกส่วนหัวออกจากเนื้อหา
นี่คือตัวอย่างที่ใช้อาร์กิวเมนต์บรรทัดคำสั่งต่อไปนี้:
- เจ้าภาพ
- ท่าเรือ
- คำสั่ง (GET หรือ POST)
- เส้นทาง (ไม่รวมข้อมูลแบบสอบถาม)
- ข้อมูลการสืบค้น (ใส่ลงในสตริงการสืบค้นสำหรับ GET และในเนื้อหาสำหรับ POST)
- รายการส่วนหัว (ความยาวเนื้อหา: เป็นไปโดยอัตโนมัติหากใช้ POST)
ดังนั้นสำหรับคำถามเดิมที่คุณจะเรียกใช้:
a.out api.somesite.com 80 GET "/apikey=ARG1&command=ARG2"
และสำหรับคำถามที่ถามในความคิดเห็นคุณจะเรียกใช้:
a.out api.somesite.com 80 POST / "name=ARG1&value=ARG2" "Content-Type: application/x-www-form-urlencoded"
นี่คือรหัส:
#include <stdio.h> /* printf, sprintf */
#include <stdlib.h> /* exit, atoi, malloc, free */
#include <unistd.h> /* read, write, close */
#include <string.h> /* memcpy, memset */
#include <sys/socket.h> /* socket, connect */
#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */
#include <netdb.h> /* struct hostent, gethostbyname */
void error(const char *msg) { perror(msg); exit(0); }
int main(int argc,char *argv[])
{
int i;
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: <host> <port> <method> <path> [<data> [<headers>]]"); exit(0); }
message_size=0;
if(!strcmp(argv[3],"GET"))
{
message_size+=strlen("%s %s%s%s HTTP/1.0\r\n");
message_size+=strlen(argv[3]);
message_size+=strlen(argv[4]);
if(argc>5)
message_size+=strlen(argv[5]);
for(i=6;i<argc;i++)
message_size+=strlen(argv[i])+strlen("\r\n");
message_size+=strlen("\r\n");
}
else
{
message_size+=strlen("%s %s HTTP/1.0\r\n");
message_size+=strlen(argv[3]);
message_size+=strlen(argv[4]);
for(i=6;i<argc;i++)
message_size+=strlen(argv[i])+strlen("\r\n");
if(argc>5)
message_size+=strlen("Content-Length: %d\r\n")+10;
message_size+=strlen("\r\n");
if(argc>5)
message_size+=strlen(argv[5]);
}
message=malloc(message_size);
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",
strlen(argv[4])>0?argv[4]:"/",
strlen(argv[5])>0?"?":"",
strlen(argv[5])>0?argv[5]:"");
else
sprintf(message,"%s %s HTTP/1.0\r\n",
strlen(argv[3])>0?argv[3]:"GET",
strlen(argv[4])>0?argv[4]:"/");
for(i=6;i<argc;i++)
{strcat(message,argv[i]);strcat(message,"\r\n");}
strcat(message,"\r\n");
}
else
{
sprintf(message,"%s %s HTTP/1.0\r\n",
strlen(argv[3])>0?argv[3]:"POST",
strlen(argv[4])>0?argv[4]:"/");
for(i=6;i<argc;i++)
{strcat(message,argv[i]);strcat(message,"\r\n");}
if(argc>5)
sprintf(message+strlen(message),"Content-Length: %d\r\n",strlen(argv[5]));
strcat(message,"\r\n");
if(argc>5)
strcat(message,argv[5]);
}
printf("Request:\n%s\n",message);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) error("ERROR opening socket");
server = gethostbyname(host);
if (server == NULL) error("ERROR, no such host");
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);
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
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);
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(sockfd);
printf("Response:\n%s\n",response);
free(message);
return 0;
}