libcurl 없이 C에서 HTTP get 요구를 작성하려면 어떻게 해야 합니까?
외부 라이브러리를 사용하지 않고 Get Request를 생성하기 위한 C 프로그램을 작성하고 싶습니다.C라이브러리나 소켓만으로 가능합니까?http 패킷(올바른 포맷을 사용하여)을 작성해서 서버에 송신하려고 생각하고 있습니다.이게 유일한 방법일까요 아니면 더 나은 방법이 있을까요?
BSD 소켓을 사용하거나 다소 제한적인 경우에는 LwIP와 같은 RTOS나 간단한 TCP 스택을 사용하여 GET/POST 요구를 형성할 수 있습니다.
많은 오픈 소스 구현이 있습니다.샘플로서 「happyhttp」를 참조해 주세요(http://scumways.com/happyhttp/happyhttp.html).네, C가 아니라 C++입니다만, 「C++ 의존」이라고 하는 것은 문자열/어레이 관리뿐이기 때문에, 순수 C로 간단하게 포토 할 수 있습니다.
HTTP는 보통 TCP 접속을 통해 전송되기 때문에 "패킷"은 없습니다.따라서 기술적으로 RFC 형식의 심볼 스트림만 존재합니다.http 요구는 보통 connect-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)
{
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);
}
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 서버는 시간 초과가 발생할 때까지 접속을 열어 둡니다(예: JavaScript, CSS 및 HTML 페이지 이후의 이미지).
- 응답을 해석하고 Content-Length 바이트를 읽으면 닫을 수 있지만 단순성을 위해 닫지 않았습니다.필요한 HTTP 응답 헤더는 다음과 같습니다.
Content-Length송신되지 않습니다.서버는, 길이를 결정하기 위해서 닫기만 하면 됩니다.
다만, 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도 제외되므로 모든 syscall을 사용자가 직접 작성해야 합니다.그렇게 엄격한 건 아닌 것 같은데.만약 당신이 다른 라이브러리에 링크하고 싶지 않고, 다른 라이브러리에서 당신의 애플리케이션으로 소스 코드를 복사하고 싶지 않다면, 소켓 API를 사용하여 TCP 스트림을 직접 처리하는 것이 당신의 최선의 방법입니다.
HTTP 요청을 생성하여 TCP 소켓 연결을 통해 전송하는 것은 답변을 읽는 것과 마찬가지로 쉽습니다.이는 매우 까다로울 수 있는 해답의 해석입니다.특히 표준 중 상당 부분을 지원하는 것을 목표로 하고 있는 경우에는 더욱 그렇습니다.임의의 웹 서버에 접속하고 있는 경우, 에러 페이지, 리다이렉트, 컨텐츠의 네고시에이션등의 문제로 인해, 당사의 생활이 곤란해질 가능성이 있습니다.한편, 서버가 정상적으로 동작하고 있는 것이 판명되어 있는 경우, 예기치 않은 서버의 응답에 대해서 간단한 에러 메세지가 문제가 없는 경우는, 그것도 지극히 간단합니다.
소켓 프로그래밍을 시도합니다.아래의 C++ 코드는, 지정한 호스트에 간단한 GET 요구를 발행해, 응답 헤더와 컨텐츠를 인쇄합니다.
Windows 10에서 테스트 완료
#include <windows.h>
#include <string>
#include <stdio.h>
#include <winsock2.h>
using std::string;
SOCKET conn;
WSADATA wsaData;
struct hostent *hp;
unsigned int addr;
struct sockaddr_in server;
long fileSize;
const int bufSize = 512;
char readBuffer[bufSize], sendBuffer[bufSize], tmpBuffer[bufSize];
char *memBuffer=NULL;
char *headerBuffer=NULL;
long totalBytesRead, thisReadSize, headerLen;
char *tmpResult=NULL, *result;
char* antenna(string host,string path);
SOCKET connectToServer(char *szServerName, WORD portNum);
int getHeaderLength(char *content);
int main(){
if(WSAStartup(0x101, &wsaData) != 0){printf("startup failure");}
memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-1}/1/public/values?alt=json");
printf("Response content:\n%s\n\n", memBuffer);
memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-2}/1/public/values?alt=json");
printf("Response content:\n%s", memBuffer);
WSACleanup();
}
char *antenna(string host, string path){
fileSize=0;
totalBytesRead=0;
memBuffer=NULL;
headerBuffer=NULL;
tmpResult=NULL,
conn = connectToServer((char*)host.c_str(), 80);
if(conn == 0){printf("No Internet connection");}
sprintf(sendBuffer, "GET %s HTTP/1.0 \r\nHost: %s\r\nConnection: close\r\n\r\n", path.c_str(),host.c_str());
send(conn, sendBuffer, strlen(sendBuffer), 0);
printf("Request Format: \n%s",sendBuffer);
while(1){
memset(readBuffer, 0, bufSize);
thisReadSize = recv (conn, readBuffer, bufSize, 0);
if ( thisReadSize <= 0 ){break;}
tmpResult = (char*)realloc(tmpResult, thisReadSize+totalBytesRead);
memcpy(tmpResult+totalBytesRead, readBuffer, thisReadSize);
totalBytesRead += thisReadSize;
}
headerLen = getHeaderLength(tmpResult);
long contenLen = totalBytesRead-headerLen;
result = new char[contenLen+1];
memcpy(result, tmpResult+headerLen, contenLen);
result[contenLen] = 0x0;
char *myTmp;
myTmp = new char[headerLen+1];
strncpy(myTmp, tmpResult, headerLen);
myTmp[headerLen] = 0;
delete(tmpResult);
headerBuffer = myTmp;
printf("Response Header: \n%s",headerBuffer);
fileSize = contenLen;
closesocket(conn);
if(fileSize != 0){
delete(memBuffer);
delete(headerBuffer);
}
return(result);
}
SOCKET connectToServer(char *szServerName, WORD portNum)
{
conn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (conn == INVALID_SOCKET){return 0;}
if(inet_addr(szServerName)==INADDR_NONE){hp=gethostbyname(szServerName);}
else{
addr=inet_addr(szServerName);
hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
}
if(hp==NULL){closesocket(conn);return 0;}
server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
server.sin_family=AF_INET;
server.sin_port=htons(portNum);
if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
{
closesocket(conn);
return 0;
}
return conn;
}
int getHeaderLength(char *content)
{
const char *srchStr1 = "\r\n\r\n", *srchStr2 = "\n\r\n\r";
char *findPos;
int ofset = -1;
findPos = strstr(content, srchStr1);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr1);
}
else
{
findPos = strstr(content, srchStr2);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr2);
}
}
return ofset;
}
(g++를 사용하여) 컴파일하려면:
g++ -static test.cpp -o test.exe -lws2_32
-lws2_32는 winsock dls와 링크하는 링커를 지정합니다.
언급URL : https://stackoverflow.com/questions/11208299/how-to-make-an-http-get-request-in-c-without-libcurl
'source' 카테고리의 다른 글
| Java 비트맵을 바이트 배열로 변환 (0) | 2022.08.11 |
|---|---|
| Store vuex가 정의되지 않은 속성 'store'를 읽을 수 없습니다." (0) | 2022.08.10 |
| C에 화살표(->) 연산자가 존재하는 이유는 무엇입니까? (0) | 2022.08.10 |
| C/C++ 매크로의 쉼표 (0) | 2022.08.10 |
| C의 단일 구조 부재 크기 (0) | 2022.08.10 |