Ich möchte ein C-Programm schreiben, um eine Get-Anforderung zu generieren, ohne externe Bibliotheken zu verwenden. Ist dies nur mit C-Bibliotheken und Sockets möglich? Ich denke daran, ein HTTP-Paket zu erstellen (mit der richtigen Formatierung) und es an den Server zu senden. Ist dies der einzig mögliche Weg oder gibt es einen besseren Weg?
Wie erstelle ich eine HTTP-Get-Anforderung in C ohne libcurl?
asudhak
Viktor Latypow
Mit BSD-Sockets oder, wenn Sie etwas eingeschränkt sind, sagen Sie, Sie haben ein RTOS, einen einfacheren TCP-Stack, wie lwIP, können Sie die GET/POST-Anforderung bilden.
Es gibt eine Reihe von Open-Source-Implementierungen. Siehe “happyhttp” als Beispiel ( http://scumways.com/happyhttp/happyhttp.html ). Ich weiß, es ist C++, nicht C, aber das einzige, was “C++-abhängig” ist, ist eine String-/Array-Verwaltung, sodass es leicht auf reines C portiert werden kann.
Achtung, es gibt keine “Pakete”, da HTTP normalerweise über die TCP-Verbindung übertragen wird, also technisch gesehen nur ein Strom von Symbolen im RFC-Format. Da HTTP-Anfragen normalerweise in einer Connect-Send-Disconnect-Weise ausgeführt werden, könnte man dies tatsächlich als “Paket” bezeichnen.
Sobald Sie einen offenen Socket (sockfd) haben, müssen Sie im Grunde “alles” so etwas tun
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);
}
}
-
Vielen Dank ! Dies tat, was ich brauchte, um es zu TUN!
– asudhak
26. Juni 2012 um 15:52 Uhr
-
@asudhak – Das funktioniert großartig, bis dieser Code in einer Unternehmensarbeitsumgebung ausgeführt werden muss, in der der einzige Internetzugang über einen Proxyserver erfolgt. Das Protokoll zum Abrufen von URLs über einen HTTP-Proxy unterscheidet sich geringfügig von dem über direktes TCP.
– selbe
27. Juni 2012 um 7:03 Uhr
-
@selbie – Sicher, die HTTP-Antworten mit Code 300 (Umleitungen) und Proxy-Zeug sind genau die Dinge, die HTTP schwierig machen. Daher kann das Anpassen von libCurl zum Ausschließen verschiedener kryptobezogener Dinge der richtige Weg sein, anstatt eine handgefertigte HTTP-Anfrage zu stellen.
– Viktor Latypov
27. Juni 2012 um 7:14 Uhr
-
@ViktorLatypov – Ich weiß, dass du das weißt. Ich habe deine Antwort nicht angeklopft. Ich wollte, dass das OP, das die ursprüngliche Frage gestellt hat, das weiß.
– selbe
27. Juni 2012 um 7:19 Uhr
-
Ich stimme beiden Seiten einer guten Antwort zu. Erstens, spielen Sie keine quadratischen Räder, wenn Sie cURL haben, das eine sehr bewährte GUTE Bibliothek ist, die auf die wichtigsten Sprachen portiert wurde. Aber manchmal macht es keinen Sinn, eine 1-MB-Bibliothek zu verwenden, wenn Sie dasselbe mit ~ 40 Codezeilen erreichen können. Vielen Dank, dass Sie sicherstellen, dass der Code funktioniert. (Ich meine streng über Test vor der Post). DANKE.
– m3nda
23. März 2015 um 7:11 Uhr
Ciro Santilli OurBigBook.com
Minimales lauffähiges Beispiel für POSIX 7
Lass uns holen http://beispiel.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);
}
Kompilieren:
gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c
Erhalten http://beispiel.com und auf stdout ausgeben:
./wget example.com
Wir sehen so etwas wie:
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>
Nach dem Drucken der Antwort hängt dieser Befehl für die meisten Server bis zum Timeout, und das ist zu erwarten:
- Entweder Server oder Client müssen die Verbindung schließen
- wir (Kunde) tun es nicht
- Die meisten HTTP-Server lassen die Verbindung bis zu einem Timeout offen und erwarten weitere Anfragen, z. B. JavaScript, CSS und Bilder, die einer HTML-Seite folgen
- Wir könnten die Antwort parsen und schließen, wenn Content-Length-Bytes gelesen werden, aber wir haben es der Einfachheit halber nicht getan. Welche HTTP-Antwortheader erforderlich sind, besagt, dass if
Content-Length
nicht gesendet wird, kann der Server einfach schließen, um die Länge zu bestimmen.
Wir könnten den Host jedoch schließen lassen, indem wir den HTTP 1.1-Standard-Header hinzufügen Connection: close
zum Server:
char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n";
Der Verbindungsteil funktioniert auch mit der IP:
host example.com
gibt:
example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
und so machen wir:
./wget 93.184.216.34
Die Antwort ist jedoch ein Fehler, weil wir das nicht setzen Host:
richtig in unserem Programm, und das ist in HTTP 1.1 erforderlich.
Getestet auf Ubuntu 18.04.
Serverbeispiele
- minimales POSIX C-Beispiel: Senden und Empfangen einer Datei in der Socket-Programmierung unter Linux mit C/C++ (GCC/G++)
- minimales Android-Java-Beispiel: Wie erstelle ich eine Socket-Verbindung in Android?
-
Der Code hängt an
read(socket_file_descriptor, buffer, BUFSIZ)
.– CroCo
5. Oktober 2017 um 22:21 Uhr
-
@CroCo siehe Quellenkommentar: „Der zweite Lesevorgang hängt für ein paar Sekunden. […]”. Entweder der Server oder der Client muss die Verbindung schließen. Wir schließen nicht, der Server also auch nicht. Dies optimiert wahrscheinlich mehrere HTTP-Anforderungen, die in einer Verbindung ausgeführt werden, was ein häufiger Fall ist (HTML abrufen, CSS abrufen, Bilder abrufen). Clients müssen im Allgemeinen die Ausgabe parsen und prüfen, ob die Antwort beendet ist, und schließen
Content-Length:
im Fall von HTTP, aber ich wollte HTTP in diesem einfachen Beispiel nicht parsen.– Ciro Santilli OurBigBook.com
6. Oktober 2017 um 0:41 Uhr
-
Danke @CiroSantilli新疆改造中心996ICU六四事件 – das ist unglaublich! Einfach hinzufügen
Connection: close
zu Ihrem Anforderungsheader, um die Verbindung nach der ersten Anforderung automatisch zu schließen, andernfalls werden standardmäßig in http1.1 Verbindungen am Leben erhalten, bis entweder der Server oder der Client geschlossen wird.– lifeofguenter
21. September 2019 um 22:34 Uhr
-
@lifeofguenter super! Der war mir nicht bekannt. In der Antwort erwähnt.
– Ciro Santilli OurBigBook.com
22. September 2019 um 8:25 Uhr
MvG
„Ohne externe Bibliotheken“ würde streng genommen auch libc ausschließen, also müssten Sie alle Systemaufrufe selbst schreiben. Ich bezweifle aber, dass Sie das so streng meinen. Wenn Sie keine Verknüpfung zu einer anderen Bibliothek herstellen und keinen Quellcode aus einer anderen Bibliothek in Ihre Anwendung kopieren möchten, ist der direkte Umgang mit dem TCP-Stream mithilfe der Socket-API der beste Ansatz.
Erstellen der HTTP anfordern und über a senden TCP-Socket-Verbindung ist einfach, ebenso wie das Lesen der Antwort. Das Analysieren der Antwort wird sehr schwierig, insbesondere wenn Sie darauf abzielen, einen relativ großen Teil des Standards zu unterstützen. Dinge wie Fehlerseiten, Weiterleitungen, Inhaltsverhandlungen und so weiter können uns das Leben ziemlich schwer machen, wenn Sie mit beliebigen Webservern sprechen. Wenn andererseits bekannt ist, dass sich der Server gut benimmt und eine einfache Fehlermeldung für jede unerwartete Serverantwort in Ordnung ist, dann ist das auch ziemlich einfach.
A5H1Q
Probieren Sie die Socket-Programmierung aus. Der folgende C++-Code gibt eine einfache GET-Anfrage an den angegebenen Host aus und gibt den Antwortheader und den Inhalt aus
Getestet unter 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;
}
Kompilieren (mit g++):
g++ -static test.cpp -o test.exe -lws2_32
-lws2_32 gibt den Linker an, der mit Winsock-DLLs verknüpft werden soll
Nein. Sie müssen zuerst die BSD-Socket-API lernen und dann alle Rohdaten manuell zusammenpacken.
– Benutzer529758
26. Juni 2012 um 13:27 Uhr