2017-01-09 3 views
2

Ich versuche zu lernen, wie das HTTP2-Protokoll funktioniert. Ich sehe, dass Apple es für ihren Push-Benachrichtigungsserver verwendet. Ich verwende die Spezifikation für den Rahmen von Discover HTTP.Senden Sie HTTP2-Frame an Server über TLS

Als Test habe ich Code geschrieben, der mit diesem Server kommunizieren soll. Allerdings bekomme ich immer wieder den Fehler, dass mir der "Einstellungen" -Frame fehlt.

Mein Code ist wie folgt:

#include <iostream> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/ioctl.h> 
#include <arpa/inet.h> 
#include <netdb.h> 
#include <dlfcn.h> 
#include <unistd.h> 
#include <openssl/ssl.h> 

#define SOCKET_ERROR -1 

struct Frame //Represents and HTTP2 Frame. 
{ 
    char len[24]; 
    char type[8]; 
    char flags[8]; 
    char identifier[31]; 
    char payload[]; 
} __attribute__((packed)); 

void writeFrame(SSL* ssl) 
{ 
    //First thing after connecting is to send the PREFACE. 
    std::string preface = "PRI * HTTP/2.0\r\n\r\n"; 
    preface += "SM\r\n\r\n"; 

    SSL_write(ssl, preface.c_str(), preface.length()); 

    //Now to send the first frame. Aka the SETTINGS frame. 
    Frame* frame = (Frame *)malloc(sizeof(Frame)); 
    memset(frame, 0, sizeof(Frame)); 

    int frameSize = 100; 
    memcpy(frame->len, &frameSize, sizeof(frameSize)); 
    memcpy(frame->type, "SETTINGS", strlen("SETTINGS")); 
    memcpy(frame->identifier, "SETTINGS", strlen("SETTINGS")); 

    SSL_write(ssl, frame, sizeof(Frame)); 

    //Read server response. 
    char buffer[10000]; 
    memset(buffer, 0, sizeof(buffer)); 
    int dataLen; 
    while ((dataLen = SSL_read(ssl, buffer, sizeof(buffer)) > 0)) 
    { 
     int i = 0; 
     while (buffer[i] >= 32 || buffer[i] == '\n' || buffer[i] == '\r') { 
      std::cout << buffer[i]; 
      i += 1; 
     } 
    } 

    //The above gives me the error: 
    //First received frame was not SETTINGS. 
    //Hex dump for first 5 bytes: 6400000000 

    //Try to POST to the server now. 
    std::string payload = "POST /3/device HTTP/2.0\r\n"; 
    payload += ":method:POST\r\n"; 
    payload += ":scheme: https\r\n"; 
    payload += "cache-control: no-cache\r\n"; 
    payload += "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36\r\n"; 
    payload += "content-type: text/plain;charset=UTF-8\r\n\r\n"; 

    int sentBytes = SSL_write(ssl, payload.c_str(), payload.length()); 

    if (sentBytes < payload.length() || sentBytes == SOCKET_ERROR) 
    { 
     return; 
    }   

    memset(buffer, 0, sizeof(buffer)); 
    while ((dataLen = SSL_read(ssl, buffer, sizeof(buffer)) > 0)) 
    { 
     int i = 0; 
     while (buffer[i] >= 32 || buffer[i] == '\n' || buffer[i] == '\r') { 
      std::cout << buffer[i]; 
      i += 1; 
     } 
    } 
} 

int main(int argc, const char * argv[]) { 

    SSL_library_init(); 
    SSL_load_error_strings(); 
    OpenSSL_add_all_algorithms(); 

    std::string address = "api.development.push.apple.com"; 
    struct addrinfo hints = {0}; 
    struct addrinfo* result = nullptr; 

    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 
    hints.ai_protocol = IPPROTO_TCP; 
    hints.ai_flags = AI_PASSIVE; 

    getaddrinfo(address.c_str(), nullptr, &hints, &result); 

    int sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); 
    if (sock == -1) 
    { 
     return -1; 
    } 

    struct sockaddr_in sockAddr; 
    sockAddr.sin_addr = reinterpret_cast<struct sockaddr_in*>(result->ai_addr)->sin_addr; 
    sockAddr.sin_family = result->ai_family; 
    sockAddr.sin_port = htons(443); 
    freeaddrinfo(result); 

    if (connect(sock, reinterpret_cast<sockaddr *>(&sockAddr), sizeof(sockAddr)) == SOCKET_ERROR) 
    { 
     close(sock); 
     return -1; 
    } 

    SSL_CTX* ctx = SSL_CTX_new(TLSv1_2_method()); 
    SSL* ssl = SSL_new(ctx); 
    SSL_set_fd(ssl, sock); 
    SSL_connect(ssl); 

    writeFrame(ssl); 

    SSL_CTX_free(ctx); 
    SSL_shutdown(ssl); 
    SSL_free(ssl); 
    close(sock); 
    return 0; 
} 

Wie kann ich die SETTINGS Rahmen und andere Rahmen an den Server senden? Was habe ich verpasst?

+0

'PRI * HTTP/2.0 \ r \ n \ r \ n' (In der Regel gibt es *** eine 'CRLF', die Header-Optionen trennt. Die letzte Option erhält *** zwei *** "CRLF". Es sieht so aus, als ob Sie für jede Option zwei 'CRLF' verdrahtet haben. (Und ich könnte falsch liegen, weil ich nie getan habe, was du tust). – jww

+1

@jww, das Vorwort des OP ist korrekt, wie von [RFC 7540] definiert (https://tools.ietf.org/html/rfc7540#section-3.5). – sbordet

Antwort

3

Sie haben mehrere Fehler.

Zunächst einmal die Frame Datenstruktur, wo die Längen der Array-Felder falsch sind. Sie scheinen ihre Länge in Bits kopiert zu haben, berichteten sie jedoch in der Frame Datenstruktur in Bytes. Sie wollen, dass diese statt:

struct Frame { 
    char len[3]; 
    char type; 
    char flags; 
    char identifier[4]; 
    char payload[]; 
} 

Darüber hinaus ist die Art des Rahmens kein String ist, noch ist die Kennung.

Schließlich ist das Format der Anfrage komplett falsch, ähnelt HTTP/1.1, während das HTTP/2-Format völlig anders ist und auf HPACK basiert.

Ich schlage vor, Sie haben eine sorgfältige Lektüre der HTTP/2 specification vor dem Schreiben weiterer Code.

+0

Ich habe diese Struktur (gepackt) verwendet, um den Settings-Frame mit type = 0x4, flags = 0 und len = 0 zu schreiben. Works kann aber nicht herausfinden, wie man den "Headers" -Frame überhaupt schreibt (type = 5, flags = 0x5, len = 020, identifier = 0001) dann payload: '" Accept ":" application/json "' funktioniert aber nicht. – Brandon

+0

@Brandon, HEADERS Rahmen muss mit [HPACK] (https://tools.ietf.org/html/rfc7540#section-6.2) geschrieben werden. – sbordet

Verwandte Themen