外部ライブラリを使用せずに Get Request を生成する C プログラムを作成したいと考えています。ソケットを使用して、Cライブラリのみを使用してこれは可能ですか? httpパケットを(適切なフォーマットを使用して)作成し、サーバーに送信することを考えています。これが唯一の可能な方法ですか、それともより良い方法がありますか?
4 に答える
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);
}
}
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);
}
コンパイル:
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 でテスト済み。
サーバーの例
- 最小限の POSIX C の例: C/C++ (GCC/G++) を使用した Linux でのソケット プログラミングでのファイルの送受信
- 最小限の Android Java の例: Androidでソケット接続を作成する方法は?
「外部ライブラリなし」と厳密に言えば、libcも除外されるため、すべてのシステムコールを自分で作成する必要があります。でも、そんなに厳しいという意味ではないかと思います。別のライブラリにリンクしたくない場合、および別のライブラリからアプリケーションにソースコードをコピーしたくない場合は、ソケットAPIを使用してTCPストリームを直接処理するのが最善の方法です。
答えを読むのと同じように、 HTTPリクエストを作成してTCPソケット接続を介して送信するのは簡単です。これは、特に標準のかなりの部分をサポートすることを目的としている場合は、非常に難しい答えを解析します。エラーページ、リダイレクト、コンテンツネゴシエーションなどは、任意のWebサーバーと通信している場合、私たちの生活を非常に困難にする可能性があります。一方、サーバーが正常に動作していることがわかっていて、予期しないサーバー応答に対して単純なエラーメッセージで問題がない場合は、それもかなり単純です。