2012-06-26 9 views
26

外部ライブラリを使用せずにGetリクエストを生成するCプログラムを作成します。これはソケットを使ってCライブラリのみを使って可能ですか?私は(適切な書式設定を使用して)httpパケットを作成してサーバーに送信することを考えています。これは唯一の方法ですか、それとも良い方法ですか?libCurlなしでCを使用したHTTP getリクエスト

+0

号。 –

答えて

22

BSDソケットを使用するか、多少制限されていると、RTOSがあるとします.lwIPのような簡単なTCPスタックがある場合は、GET/POSTリクエストを作成できます。

多くのオープンソースの実装があります。 "happyhttp"をサンプル(http://scumways.com/happyhttp/happyhttp.html)として参照してください。私はそれがC++ではなくC言語であることを知っていますが、C++に依存する唯一のものは文字列/配列管理なので簡単に純粋なCに移植されます。

"パケット" HTTPは通常TCP接続を介して転送されるため、技術的にはRFC形式のシンボルストリームしか存在しません。 http要求は通常connect-send-disconnect方式で行われるため、実際にはこれを「パケット」と呼びます。

あなたが開いたソケット(数sockfd)を持ったら基本的に、あなたがしなければならない「すべて」は、厳密にもlibcを排除するに言えば、あなたがしたい「任意の外部ライブラリがなければ、」

char sendline[MAXLINE + 1], recvline[MAXLINE + 1]; 
char* ptr; 

size_t n; 

/// Form request 
snprintf(sendline, MAXSUB, 
    "GET %s HTTP/1.0\r\n" // POST or GET, both tested and works. Both HTTP 1.0 HTTP 1.1 works, but sometimes 
    "Host: %s\r\n"  // but sometimes HTTP 1.0 works better in localhost type 
    "Content-type: application/x-www-form-urlencoded\r\n" 
    "Content-length: %d\r\n\r\n" 
    "%s\r\n", page, host, (unsigned int)strlen(poststr), poststr); 

/// Write the request 
if (write(sockfd, sendline, strlen(sendline))>= 0) 
{ 
    /// Read the response 
    while ((n = read(sockfd, recvline, MAXLINE)) > 0) 
    { 
     recvline[n] = '\0'; 

     if(fputs(recvline,stdout) == EOF) { cout << ("fputs erros"); } 
     /// Remove the trailing chars 
     ptr = strstr(recvline, "\r\n\r\n"); 

     // check len for OutResponse here ? 
     snprintf(OutResponse, MAXRESPONSE,"%s", ptr); 
    }   
} 
+0

ありがとう!これは私がそれを必要とするものでした! – asudhak

+3

@asudhak - このコードは、唯一のインターネットアクセスがプロキシサーバーを経由している企業の作業環境で実行されるまでは素晴らしいです。 HTTPプロキシ経由でURLを取得するプロトコルは、直接TCP経由とは若干異なります。 – selbie

+0

@selbie - もちろん、コード300(リダイレクト)とプロキシのHTTP応答は、HTTPを困難にするものです。だから、他の暗号関連のものを除外するためにlibCurlをテイラー化することは、手作りのHTTPリクエストの代わりに行く方法になる可能性があります。 –

3

のようなものです自分ですべてのシステムコールを書き込む必要があります。私はあなたがそれを厳密に意味しているのか疑問です。別のライブラリにリンクしたくない場合、別のライブラリのソースコードをアプリケーションにコピーしたくない場合は、ソケットAPIを使用してTCPストリームを直接処理するのが最善の方法です。

HTTPリクエストを作成してTCP socket connectionに送信することは、解答を読むのと同じように簡単です。あなたが標準の合理的に大きな部分をサポートすることを目指している場合、特に難しいと思われる答えを解析しています。エラーページ、リダイレクト、コンテンツネゴシエーションなどのようなものは、任意のWebサーバーと話している場合、私たちの生活を非常に困難にする可能性があります。一方、サーバーが正常に動作していることがわかっていて、単純なエラーメッセージが予期せぬサーバーの応答であれば、それはかなり簡単です。

7

POSIX 7最小の実行可能な例

#define _XOPEN_SOURCE 700 

#include <assert.h> 
#include <stdbool.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#include <arpa/inet.h> 
#include <netdb.h> /* getprotobyname */ 
#include <netinet/in.h> 
#include <sys/socket.h> 
#include <unistd.h> 

int main(int argc, char** argv) { 
    char buffer[BUFSIZ]; 
    enum CONSTEXPR { MAX_REQUEST_LEN = 1024}; 
    char request[MAX_REQUEST_LEN]; 
    char request_template[] = "GET/HTTP/1.1\r\nHost: %s\r\n\r\n"; 
    struct protoent *protoent; 
    char *hostname = "example.com"; 
    in_addr_t in_addr; 
    int request_len; 
    int socket_file_descriptor; 
    ssize_t nbytes_total, nbytes_last; 
    struct hostent *hostent; 
    struct sockaddr_in sockaddr_in; 
    unsigned short server_port = 80; 

    if (argc > 1) 
     hostname = argv[1]; 
    if (argc > 2) 
     server_port = strtoul(argv[2], NULL, 10); 

    request_len = snprintf(request, MAX_REQUEST_LEN, request_template, hostname); 
    if (request_len >= MAX_REQUEST_LEN) { 
     fprintf(stderr, "request length large: %d\n", request_len); 
     exit(EXIT_FAILURE); 
    } 

    /* Build the socket. */ 
    protoent = getprotobyname("tcp"); 
    if (protoent == NULL) { 
     perror("getprotobyname"); 
     exit(EXIT_FAILURE); 
    } 
    socket_file_descriptor = socket(AF_INET, SOCK_STREAM, protoent->p_proto); 
    if (socket_file_descriptor == -1) { 
     perror("socket"); 
     exit(EXIT_FAILURE); 
    } 

    /* Build the address. */ 
    hostent = gethostbyname(hostname); 
    if (hostent == NULL) { 
     fprintf(stderr, "error: gethostbyname(\"%s\")\n", hostname); 
     exit(EXIT_FAILURE); 
    } 
    in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list))); 
    if (in_addr == (in_addr_t)-1) { 
     fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list)); 
     exit(EXIT_FAILURE); 
    } 
    sockaddr_in.sin_addr.s_addr = in_addr; 
    sockaddr_in.sin_family = AF_INET; 
    sockaddr_in.sin_port = htons(server_port); 

    /* Actually connect. */ 
    if (connect(socket_file_descriptor, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) { 
     perror("connect"); 
     exit(EXIT_FAILURE); 
    } 

    /* Send HTTP request. */ 
    nbytes_total = 0; 
    while (nbytes_total < request_len) { 
     nbytes_last = write(socket_file_descriptor, request + nbytes_total, request_len - nbytes_total); 
     if (nbytes_last == -1) { 
      perror("write"); 
      exit(EXIT_FAILURE); 
     } 
     nbytes_total += nbytes_last; 
    } 

    /* Read the response. 
    * 
    * The second read hangs for a few seconds, until the server times out. 
    * 
    * Either server or client has to close the connection. 
    * 
    * We are not doing it, and neither is the server, likely to make serving the page faster 
    * to allow fetching HTML, CSS, Javascript and images in a single connection. 
    * 
    * The solution is to parse Content-Length to see if the HTTP response is over, 
    * and close it then. 
    * 
    * http://stackoverflow.com/a/25586633/895245 says that if Content-Length 
    * is not sent, the server can just close to determine length. 
    **/ 
    fprintf(stderr, "debug: before first read\n"); 
    while ((nbytes_total = read(socket_file_descriptor, buffer, BUFSIZ)) > 0) { 
     fprintf(stderr, "debug: after a read\n"); 
     write(STDOUT_FILENO, buffer, nbytes_total); 
    } 
    fprintf(stderr, "debug: after last read\n"); 
    if (nbytes_total == -1) { 
     perror("read"); 
     exit(EXIT_FAILURE); 
    } 

    close(socket_file_descriptor); 
    exit(EXIT_SUCCESS); 
} 

使用

コンパイル:

gcc -o wget wget.c 

はstdoutにhttp://example.comと出力を得る:

./wget example.com 

IP:

./wget 104.16.118.182 

このコマンドタイムアウトまでのほとんどのサーバーのためのハング、そしてそれが期待されます。

  • サーバーまたはクライアントが
  • ほとんどのHTTPサーバが接続を残して、接続を閉じる必要がありますさらなる要求が予想されるタイムアウトまでオープンします。JavaScriptやHTMLのページ次のCSSや画像
  • のContent-Lengthバイトが読み込まれたときに、我々は応答を解析し、近いこともできますが、我々はシンプル
  • のUbuntu 15.10でテスト

のためではありませんでした。

でサーバー側の例:上流Send and Receive a file in socket programming in Linux with C/C++ (GCC/G++)

GitHubの:あなたはすべての生データを一緒にパックし、手動で、その後、最初のBSDソケットAPIを学ぶ必要があるhttps://github.com/cirosantilli/cpp-cheat/blob/88d0c30681114647cce456c2e17aa2c5b31abcd0/posix/socket/wget.c

+0

コードは 'read(socket_file_descriptor、buffer、BUFSIZ)'でハングします。 – CroCo

+0

@CroCoソースのコメントを参照してください: "2番目の読み取りは数秒間ハングします[...]"。サーバーまたはクライアントのいずれかが接続を閉じる必要があります。我々はクローズしていないので、どちらもサーバーではありません。これは、1つの接続で行われる複数のHTTP要求を最適化する可能性があります。これは一般的なケースです(HTMLの取得、CSSの取得、画像の取得)。クライアントは一般に出力を解析し、HTTPの場合は 'Content-Length:'を使用して応答が終了して閉じることを確認する必要がありますが、この単純な例ではHTTPを解析したくありません。 –

関連する問題