54

外部ライブラリを使用せずに Get Request を生成する C プログラムを作成したいと考えています。ソケットを使用して、Cライブラリのみを使用してこれは可能ですか? httpパケットを(適切なフォーマットを使用して)作成し、サーバーに送信することを考えています。これが唯一の可能な方法ですか、それともより良い方法がありますか?

4

4 に答える 4

39

BSD ソケットを使用するか、多少制限がある場合は、RTOS や lwIP などの単純な TCP スタックを使用して、GET/POST 要求を作成できます。

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

HTTP は通常 TCP 接続を介して転送されるため、「パケット」がないことに注意してください。技術的には、RFC 形式のシンボルのストリームのみが存在します。http 要求は通常、接続 - 送信 - 切断の方法で行われるため、実際にはこれを「パケット」と呼ぶことがあります。

基本的に、開いているソケット (sockfd) を「すべて」取得したら、次のようなことを行う必要があります。

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)
        {
            printf("fputs() error\n");
        }

        /// Remove the trailing chars
        ptr = strstr(recvline, "\r\n\r\n");

        // check len for OutResponse here ?
        snprintf(OutResponse, MAXRESPONSE,"%s", ptr);
    }          
}
于 2012-06-26T13:44:49.217 に答える
29

POSIX 7 の最小限の実行可能な例

http://example.comをフェッチしましょう。

wget.c

#define _XOPEN_SOURCE 700
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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. */
    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);
}

GitHub アップストリーム.

コンパイル:

gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c

http://example.comを取得し、stdout に出力します。

./wget example.com

次のようなものが表示されます。

debug: before first read
debug: after a read
HTTP/1.1 200 OK
Age: 540354
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Tue, 02 Feb 2021 15:21:14 GMT
Etag: "3147526947+ident"
Expires: Tue, 09 Feb 2021 15:21:14 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (nyb/1D11)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256

<!doctype html>
<html>
...
</html>

応答を出力した後、このコマンドはほとんどのサーバーでタイムアウトになるまでハングしますが、これは予想されることです。

  • サーバーまたはクライアントのいずれかが接続を閉じる必要があります
  • 私たち(クライアント)はそれをしていません
  • ほとんどの HTTP サーバーは、HTML ページに続く JavaScript、CSS、および画像などのさらなる要求を予期して、タイムアウトになるまで接続を開いたままにします。
  • 応答を解析し、Content-Length バイトが読み取られたときに閉じることもできますが、簡単にするためにそうしませんでした。What HTTP response headers are requiredは、Content-Length 送信されない場合、サーバーを閉じて長さを判断できることを示しています。

Connection: closeただし、HTTP 1.1 標準ヘッダーを追加してサーバーに渡すことで、ホストを閉じることができます。

char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n";

接続部分は IP でも機能します。

host example.com

与えます:

example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946

したがって、次のようにします。

./wget 93.184.216.34

Host:ただし、プログラムで を適切に設定していないため、応答はエラーになります。これは HTTP 1.1 で必要です

Ubuntu 18.04 でテスト済み。

サーバーの例

于 2016-02-28T09:06:17.963 に答える
7

「外部ライブラリなし」と厳密に言えば、libcも除外されるため、すべてのシステムコールを自分で作成する必要があります。でも、そんなに厳しいという意味ではないかと思います。別のライブラリにリンクしたくない場合、および別のライブラリからアプリケーションにソースコードをコピーしたくない場合は、ソケットAPIを使用してTCPストリームを直接処理するのが最善の方法です。

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

于 2012-06-26T13:58:32.670 に答える