HTTPのリクエストメッセージを
Webサーバに送信して、
返ってきたレスポンスメッセージを
表示させていきます。
今回は試しに
リクエストラインには、
"HEAD / HTTP/1.1"
(メソッド URL HTTPバージョン)
を指定しておくります。
HEADメソッド:
リクエストメッセージの内、
ステータスラインとヘッダのみ取得。
真ん中のはURLを指定します。
「 / 」はルートディレクトリです。
最後にHTTPのバージョンを指定します。
HTTP2.0に現状まだ、
対応していない場合もあるので、
HTTP1.1を指定しています。
ソースはこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<netdb.h> #include<netinet/in.h> #include<sys/socket.h> #include<arpa/inet.h> void dump(const char *, const int); //受信パケットの表示用 int main(int argc, char *argv[]){ int soc, len; struct hostent *host_info; //gethostbyname2の戻り値を入れる struct sockaddr_in server; //サーバ情報の登録用 char buf[4096], buf_ip[INET_ADDRSTRLEN]; //受信用バッファとIPアドレス格納用バッファ char data[19] = "HEAD / HTTP/1.1\r\n\r\n"; //サーバに送信するリクエスト if(argc<2){ printf("[-]How to use: %s<Domain>\n", argv[0]); return -1; } //ソケット生成 if((soc=socket(PF_INET, SOCK_STREAM, 0))<0){ perror("[-]socket()"); return -1; } //入力したホスト名からアドレスに変換 if((host_info=gethostbyname2(argv[1], AF_INET))==NULL){ perror("[-]gethostbyname2()"); return -1; } //サーバ情報を登録 memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; //IPv4 server.sin_addr = *((struct in_addr *)host_info->h_addr); //hostent構造体からアドレスをキャストしつつ取り出す server.sin_port = htons(80); //ポート番号 printf("[+]Trying %s...\n", inet_ntop(AF_INET, &server.sin_addr, buf_ip, sizeof(buf_ip))); if(connect(soc, (struct sockaddr *)&server, sizeof(server))==-1){ perror("[-]connect()"); return -1; } printf("[+]connected to %s\n", argv[1]); //データ送信 if((send(soc, data, sizeof(data), 0))<0){ perror("[-]send()"); return -1; } printf("[+]%s", data); //サーバから受信 while((len=recv(soc, buf, sizeof(buf), 0))>0){ dump(buf, len); //受信したリスポンスパケットを表示 } return 0; } void dump(const char *data_buffer, const int length){ for(int i=0; i<length; i++){ if((data_buffer[i]=='\r')&&(data_buffer[i+1]=='\n')){ printf("\n"); i++; } else printf("%c", data_buffer[i]); } } |
リクエストラインを指定する際に
\r\n (CRLF) を足す必要があります。
'\r' は、CR: キャリッジリターン
'\n' は、LF: ラインフィード
と呼ばれています。
16進数だと0x0dと0x0aです。
これらは行末表現で、
HTTPプロトコルでは、
これらで終了している必要があります。
したがって、
送信バッファにも受信バッファにも
行末表現が含まれています。
今回のソースは、
基本的なクライアント側の
ネットワークプログラミングと
HTTPの基礎知識があれば、
理解しやすいかと思います。
まずは既存のコマンドで
正確な情報を表示してみましょう。
telnet www.google.com 80
telnetコマンドで、
googleのWebサーバに接続します。
そしてHEAD / HTTP/1.1をタイプ後、
2回エンターキーを押します。
2回のエンターが\r\nに相当します。

これを踏まえて、
ソースを実行してみます。
実行時にwebサーバを指定します。
ポートはソース内で指定済みです。

HTTP/1.1 200 OK
(レスポンスステータスライン)
の左にある、
最初の2バイトが文字化けしました。
HTTPのフォーマットを調べれば、
最初の2バイトが何を意味するのか
判明するかと思います。
上記の点以外は、
コマンドでやったときと、
同じ結果になりました。
ちなみにGETメソッドで
やってみてもコマンドと
同じ結果になりました。
最後まで読んでいただきありがとうございました。