2017-01-09 5 views
2

Я пытаюсь узнать, как работает протокол HTTP2. Я вижу, что Apple использует его для своего сервера push-уведомлений. Я использую спецификацию для фрейма от Discover HTTP.Отправлять кадр HTTP2 на сервер через TLS

В качестве теста я написал код, который должен связываться с этим сервером. Тем не менее, я продолжаю получать сообщение об ошибке, что мне не хватает рамки «Настройки».

Мой код выглядит следующим образом:

#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; 
} 

Как я могу отправить SETTINGS кадр и другие кадры на сервер? Что я пропустил?

+0

'PRI * HTTP/2.0 \ r \ n \ r \ n' (и др.) - Как правило, существует *** один ***' CRLF', разделяющий параметры заголовка. Окончательный вариант получает *** два *** 'CRLF'. Похоже, вы подключили два 'CRLF' для каждого варианта. (И я мог ошибаться, потому что я никогда не делал то, что вы делаете). – jww

+1

@jww, предисловие OP правильное, как определено в [RFC 7540] (https://tools.ietf.org/html/rfc7540#section-3.5). – sbordet

ответ

3

У вас есть несколько ошибок.

Прежде всего, структура данных Frame, где длины полей массива являются неправильными. Вы, кажется, скопировали их длину в битах, но сообщили о них в структуре данных Frame в байтах. Вы хотите вместо этого:

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

Кроме того, тип кадра не является строкой, ни идентификатор.

И, наконец, формат запроса полностью неправильный, похожий на HTTP/1.1, в то время как формат HTTP/2 полностью отличается и основан на HPACK.

Я предлагаю вам внимательно прочитать HTTP/2 specification, прежде чем писать дополнительный код.

+0

Я использовал эту структуру (упакованную), чтобы написать рамку настроек с типом = 0x4, flags = 0 и len = 0. Работает, но не может понять, как писать кадр «Заголовки» вообще (type = 5, flags = 0x5, len = 020, идентификатор = 0001), тогда полезная нагрузка: «Принимать»: «application/json» ', но не работает. – Brandon

+0

@Brandon, кадр HEADERS должен быть записан с использованием [HPACK] (https://tools.ietf.org/html/rfc7540#section-6.2). – sbordet

Смежные вопросы