通过TLS将HTTP2帧发送到服务器

时间:2017-01-09 00:11:20

标签: c++ sockets ssl http2

我正在尝试了解HTTP2协议的工作原理。我看到Apple将它用作推送通知服务器。我正在使用Discover HTTP中的框架规范。

作为测试,我编写了应该与该服务器通信的代码。但是,我不断收到我错过了"设置"帧。

我的代码如下:

#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帧和其他帧发送到服务器?我错过了什么?

1 个答案:

答案 0 :(得分: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