Wie erstelle ich eine HTTP-Get-Anforderung in C ohne libcurl?

Lesezeit: 10 Minuten

Benutzeravatar von asudhak
asudhak

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?

  • Nein. Sie müssen zuerst die BSD-Socket-API lernen und dann alle Rohdaten manuell zusammenpacken.

    Benutzer529758

    26. Juni 2012 um 13:27 Uhr

Benutzeravatar von Viktor Latypov
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 Benutzeravatar von OurBigBook.com
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);
}

GitHub-Upstream.

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

Benutzeravatar von MvG
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.

Benutzeravatar von A5H1Q
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

1411830cookie-checkWie erstelle ich eine HTTP-Get-Anforderung in C ohne libcurl?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy