外部ライブラリを使用せずにGetリクエストを生成するCプログラムを作成します。これは、ソケットを使用して、Cライブラリのみを使用して可能ですか?私は(適切なフォーマットを使用して)HTTPパケットを作成し、サーバーに送信することを考えています。これが唯一の可能な方法ですか、それともより良い方法がありますか?
BSDソケットを使用するか、ある程度制限されている場合、RTOS、より単純なTCPスタック、lwIPのように、GET/POSTリクエストを作成できます。
多くのオープンソースの実装があります。サンプルとして「happyhttp」を参照してください( http://scumways.com/happyhttp/happyhttp.html )。私は知っている、それはCではなくC++ですが、「C++に依存する」唯一のものは文字列/配列管理なので、純粋なCに簡単に移植できます。
HTTPは通常TCP接続を介して転送されるため、技術的にはRFC形式のシンボルのストリームのみが存在するため、「パケット」はありません。httpリクエストは通常接続で行われるため-send-disconnect方式では、実際にこれを「パケット」と呼ぶ場合があります。
基本的に、開いたソケット(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) { cout << ("fputs erros"); }
/// 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
このコマンドは、ほとんどのサーバーでタイムアウトするまでハングします。
Content-Length
が送信されない場合、サーバーが長さを決定するために閉じることができることを示します。接続部分は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でテスト済み。
サーバーの例
「外部ライブラリなしで」厳密に言えばlibcも除外されるため、すべてのsyscallを自分で記述する必要があります。しかし、あなたがそれをそれほど厳密に意味しているとは思わない。別のライブラリにリンクしたくない場合、およびソースコードを別のライブラリからアプリケーションにコピーしたくない場合は、ソケットAPIを使用してTCPストリームを直接処理します最善のアプローチ。
[〜#〜] http [〜#〜] リクエストを作成し、それを TCPソケット接続 で送信するのは簡単です。答えを読むのも簡単です。特に、標準のかなり大きな部分をサポートすることを目指している場合には、答えを解析することは本当に難しいことです。エラーページ、リダイレクト、コンテンツネゴシエーションなどは、任意のWebサーバーと通信している場合、私たちの生活を非常に困難にする可能性があります。一方、サーバーが正常に動作することがわかっていて、予期しないサーバーの応答に対して単純なエラーメッセージで十分であれば、それも合理的に単純です。