C socket 傳送HTTP請求
HTTP請求頭部樣例:
GET http://www.baidu.com/ HTTP/1.1
Accept: html/text
Host: 220.181.6.175:80
Connection: Close
這是一個請求百度頁面的頭部。
屬性和值的命名中間用:和空格隔開,結尾使用\r\n,頭部結束使用\r\n\r\n
GET表示採用GET方法,當然我們常見的還有POST等其他方法,具體每個方法的意義可以檢視RFC文件(附件)。
http://www.baidu.com/請求URL的絕對地址,如果使用相對地址可以改為/或者/index.html.注:後面的/不能少。
HTTP/1.1 版本號
Accept 接受響應的型別
Host請求的主機地址和埠
Connection:如果值為close則告訴伺服器,當本次資料傳遞完畢以後,就會斷開TCP連結。如果值為Keep-Alive則告訴伺服器,資料傳輸結束後,本次連結不斷開,等待後續請求。
用SOCKET模擬遞交HTTP請求步驟:
1.首先建立和HTTP伺服器的TCP連結
2.組織HTTP請求
3.傳送請求
4.獲取響應
一個下載百度首頁的例子:
#include "stdlib.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include "netdb.h"
#include "string.h"
#include "arpa/inet.h"
#include "ctype.h"
#include "stdio.h"
#include "sys/stat.h"
#include "fcntl.h"
void send_and_recv(int sockfd, char * url, char * fun_type, char * accept_type, char * ip, int port, char * file_loc, char * body, char * connection_type);
//sockfd表示TCP連結的套接字,url請求服務的相對或者絕對地址,fun_type請求方法,accept_type接受類 型,ip,port請求的伺服器的地址和埠,file_loc下載檔案存放位置,body請求的主體,connection_type用來指定 connection的型別
int main() {
int sockfd;
struct sockaddr_in serv_socket;
int port = 80;
char ip[] = "220.181.6.175"; //ip地址,可以通過gethostbyname來獲取
char file_loc[] = "/programe/http/temp.html"; //下載的存放位置
bzero(&serv_socket, sizeof(struct sockaddr_in));
serv_socket.sin_family = AF_INET;
serv_socket.sin_port = htons(port);
inet_pton(AF_INET, ip, &serv_socket.sin_addr);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
int flag = connect(sockfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket)); //建立和HTTP伺服器的TCP連結
if(flag < 0) {
printf("connect error!!! flag = %d\n", flag);
exit(1);
}
send_and_recv(sockfd, "http://www.baidu.com/", "GET", "html/text", ip, port, file_loc, NULL, "Close"); //下載的主體函式
close(sockfd);
exit(0);
}
void send_and_recv(int sockfd, char * url, char * fun_type, char * accept_type, char * ip, int port, char * file_loc, char * body, char * connection_type) {
char * request = (char *) malloc (4 * 1024 * sizeof(char));
if(body)
sprintf(request, "%s %s HTTP/1.1\r\nAccept: %s\r\nHost: %s:%d\r\nConnection: %s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-L
ength: %d\r\n\r\n%s", fun_type, url, accept_type, ip, port, connection_type, body, strlen(body));
else
sprintf(request, "%s %s HTTP/1.1\r\nAccept: %s\r\nHost: %s:%d\r\nConnection: %s\r\n\r\n", fun_type, url, accept_type, ip, port, connection_type
);
//以上是在組織請求的頭部,打印出的結果就是文章開頭所寫
int send = write(sockfd, request, strlen(request));
printf("%s", request);
free(request);
char * response = (char *) malloc (1024 * sizeof(char));
if(file_loc) {
int file = open(file_loc, O_RDWR | O_APPEND);
int length;
do {
length = read(sockfd, response, 1024);
char * loc = strstr(response, "\r\n\r\n"); //截獲返回頭部,以\r\n\r\n為標識
if(loc) {
int loci = loc - response + 4;
write(1, response, loci);//如果是響應頭部就列印至螢幕
write(file, loc, length - loci);//如果是響應主體就寫入檔案
} else {
write(file, response, length);
}
if(!length)//注意,因為之前採用的是close方法,也就是說一旦傳輸資料完畢,則伺服器端會斷開連結,則read函式會返回0,所以這裡 會退出迴圈。如果採用的是Keep-Alive則伺服器不關閉TCP連結,也就說程式將會被阻塞在read函式中,因此要注意的是自己判斷是否讀到了響應 的結尾,然後在再次呼叫read之前退出迴圈。
break;
} while(1);
close(file);
} else {
int length;
do {
length = read(sockfd, response, 1024);
printf("%s", response);
if(!length)
break;
} while(1);
}
free(response);
}
之前的頭部比較簡單,在傳送請求的時候,我們常常會遞交表單,如果採用GET方法,則可以通過URL傳遞引數。如果採用POST,則新的HTTP請求看上去應該是這樣。(帶COOKIE)
POST http://192.168.1.154:8888/httpstudy2/servlet/IndexServlet HTTP/1.1
Accept: html/text
Host: 192.168.1.154:8888
Cookie: username=difa; password=yuna
Connection: Close
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
username=hello&password=world
Content-Type表示主體型別
Content-Length表示主體長度,不包括頭部。
整個傳送的HTTP請求應該是:
POST http://192.168.1.154:8888/httpstudy2/servlet/IndexServlet HTTP/1.1\r\nAccept: html/text\r\nHost: 192.168.1.154:8888\r\nCookie: username=difa; password=yuna\r\nConnection: Close\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 29\r\n\r\nusername=hello&password=world
HTTP 請求報文
HTTP Command: //方法欄位,說明其使用的是GET 方法
URI: / //URL 欄位,傳送請求至儲存該網站的伺服器。
HTTP Version: //http 協議版本欄位,用是的http/1.1 版本
Accept: //指示可被接受的請求迴應的介質類型範圍列表。
Accept-Language: //限制了請求迴應中首選的語言為簡體中文,否則使用預設值。
Accept-Encoding: //限制了迴應中可接受的內容編碼值,指示附加內容解碼方式為gzip,deflate.
User-Agent: //定義使用者代理,即傳送請求的瀏覽器型別為Mozilla/4.0
Host: /r/n定義了目標所在的主機
Connection: Keep-Alive/r/n //告訴伺服器使用持久連線
HTTP 迴應報文
HTTP Version: HTTP/1.1 //伺服器用的是HTTP/1.1 版本
HTTP Status: 200 //請求成功,資訊可以讀取,包含在響應的報文中
Date: //指伺服器從檔案系統中檢索到該物件,插入到響應報文,併發送該響應報文的時間
Server: //表明刻報文是由一個Apache/2.0.52 的伺服器產生的
X-Powered-By: //表明是使用PHP(版本)的動態網頁
Set- cookie: //
Vary: //
Content-Length: //表明實體的長度
Connection: //告訴客戶機在報文傳送完畢後仍然保持連線
Content-Type: //表明實體中的物件是html 文件
Binary Data: //二進位制資料
說明:在伺服器給的迴應請求中,我們可以從狀態碼中看到訪問的相關資訊。狀態碼錶示響應型別,常用的有:
1×× 保留
2×× 表示請求成功地接收
3×× 為完成請求客戶需進一步細化請求
4×× 客戶錯誤
5×× 伺服器錯誤
狀態程式碼 |
狀態資訊 |
含義 |
100 |
Continue |
初始的請求已經接受,客戶應當繼續傳送請求的其餘部分。(HTTP 1.1新) |
101 |
Switching Protocols |
伺服器將遵從客戶的請求轉換到另外一種協議(HTTP 1.1新) |
200 |
OK |
一切正常,對GET和POST請求的應答文件跟在後面。 |
201 |
Created |
伺服器已經建立了文件,Location頭給出了它的URL。 |
202 |
Accepted |
已經接受請求,但處理尚未完成。 |
203 |
Non-Authoritative Information |
文件已經正常地返回,但一些應答頭可能不正確,因為使用的是文件的拷貝(HTTP 1.1新)。 |
204 |
No Content |
沒有新文件,瀏覽器應該繼續顯示原來的文件。如果使用者定期地重新整理頁面,而Servlet可以確定使用者文件足夠新,這個狀態程式碼是很有用的。 |
205 |
Reset Content |
沒有新的內容,但瀏覽器應該重置它所顯示的內容。用來強制瀏覽器清除表單輸入內容(HTTP 1.1新)。 |
206 |
Partial Content |
客戶傳送了一個帶有Range頭的GET請求,伺服器完成了它(HTTP 1.1新)。 |
300 |
Multiple Choices |
客戶請求的文件可以在多個位置找到,這些位置已經在返回的文件內列出。如果伺服器要提出優先選擇,則應該在Location應答頭指明。 |
301 |
Moved Permanently |
客戶請求的文件在其他地方,新的URL在Location頭中給出,瀏覽器應該自動地訪問新的URL。 |
302 |
Found |
類似於301,但新的URL應該被視為臨時性的替代,而不是永久性的。注意,在HTTP1.0中對應的狀態資訊是“Moved Temporatily”。 出現該狀態程式碼時,瀏覽器能夠自動訪問新的URL,因此它是一個很有用的狀態程式碼。 注意這個狀態程式碼有時候可以和301替換使用。例如,如果瀏覽器錯誤地請求http://host/~user(缺少了後面的斜槓),有的伺服器返回301,有的則返回302。 嚴格地說,我們只能假定只有當原來的請求是GET時瀏覽器才會自動重定向。請參見307。 |
303 |
See Other |
類似於301/302,不同之處在於,如果原來的請求是POST,Location頭指定的重定向目標文件應該通過GET提取(HTTP 1.1新)。 |
304 |
Not Modified |
客戶端有緩衝的文件併發出了一個條件性的請求(一般是提供If-Modified-Since頭表示客戶只想比指定日期更新的文件)。伺服器告訴客戶,原來緩衝的文件還可以繼續使用。 |
305 |
Use Proxy |
客戶請求的文件應該通過Location頭所指明的代理伺服器提取(HTTP 1.1新)。 |
307 |
Temporary Redirect |
和302(Found)相同。許多瀏覽器會錯誤地響應302應答進行重定向,即使原來的請求是POST,即使它實際上只能在POST請求的應答是303時才能重定向。由於這個原因,HTTP 1.1新增了307,以便更加清除地區分幾個狀態程式碼:當出現303應答時,瀏覽器可以跟隨重定向的GET和POST請求;如果是307應答,則瀏覽器只能跟隨對GET請求的重定向。(HTTP 1.1新) |
400 |
Bad Request |
請求出現語法錯誤。 |
401 |
Unauthorized |
客戶試圖未經授權訪問受密碼保護的頁面。應答中會包含一個WWW-Authenticate頭,瀏覽器據此顯示使用者名稱字/密碼對話方塊,然後在填寫合適的Authorization頭後再次發出請求。 |
403 |
Forbidden |
資源不可用。伺服器理解客戶的請求,但拒絕處理它。通常由於伺服器上檔案或目錄的許可權設定導致。 |
404 |
Not Found |
無法找到指定位置的資源。這也是一個常用的應答。 |
405 |
Method Not Allowed |
請求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)對指定的資源不適用。(HTTP 1.1新) |
406 |
Not Acceptable |
指定的資源已經找到,但它的MIME型別和客戶在Accpet頭中所指定的不相容(HTTP 1.1新)。 |
407 |
Proxy Authentication Required |
類似於401,表示客戶必須先經過代理伺服器的授權。(HTTP 1.1新) |
408 |
Request Timeout |
在伺服器許可的等待時間內,客戶一直沒有發出任何請求。客戶可以在以後重複同一請求。(HTTP 1.1新) |
409 |
Conflict |
通常和PUT請求有關。由於請求和資源的當前狀態相沖突,因此請求不能成功。(HTTP 1.1新) |
410 |
Gone |
所請求的文件已經不再可用,而且伺服器不知道應該重定向到哪一個地址。它和404的不同在於,返回407表示文件永久地離開了指定的位置,而404表示由於未知的原因文件不可用。(HTTP 1.1新) |
411 |
Length Required |
伺服器不能處理請求,除非客戶傳送一個Content-Length頭。(HTTP 1.1新) |
412 |
Precondition Failed |
請求頭中指定的一些前提條件失敗(HTTP 1.1新)。 |
413 |
Request Entity Too Large |
目標文件的大小超過伺服器當前願意處理的大小。如果伺服器認為自己能夠稍後再處理該請求,則應該提供一個Retry-After頭(HTTP 1.1新)。 |
414 |
Request URI Too Long |
URI太長(HTTP 1.1新)。 |
416 |
Requested Range Not Satisfiable |
伺服器不能滿足客戶在請求中指定的Range頭。(HTTP 1.1新) |
500 |
Internal Server Error |
伺服器遇到了意料不到的情況,不能完成客戶的請求。 |
501 |
Not Implemented |
伺服器不支援實現請求所需要的功能。例如,客戶發出了一個伺服器不支援的PUT請求。 |
502 |
Bad Gateway |
伺服器作為閘道器或者代理時,為了完成請求訪問下一個伺服器,但該伺服器返回了非法的應答。 |
503 |
Service Unavailable |
伺服器由於維護或者負載過重未能應答。例如,Servlet可能在資料庫連線池已滿的情況下返回503。伺服器返回503時可以提供一個Retry-After頭。 |
504 |
Gateway Timeout |
由作為代理或閘道器的伺服器使用,表示不能及時地從遠端伺服器獲得應答。(HTTP 1.1新) |
505 |
HTTP Version Not Supported |
伺服器不支援請求中所指明的HTTP版本。(HTTP 1.1新) |