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



我正在尝试学习HTTP2协议的工作原理。我看到苹果将其用于他们的推送通知服务器。我正在使用来自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.0rnrn";
    preface += "SMrnrn";
    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.0rn";
    payload += ":method:POSTrn";
    payload += ":scheme: httpsrn";
    payload += "cache-control: no-cachern";
    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.36rn";
    payload += "content-type: text/plain;charset=UTF-8rnrn";
    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帧和其他帧发送到服务器?我错过了什么?

您有几个错误。

首先,Frame数据结构,其中数组字段的长度是错误的。您似乎以位为单位复制了它们的长度,但在Frame数据结构中以字节为单位报告了它们。你想要这个:

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

此外,帧的类型不是字符串,标识符也不是。

最后,请求的格式是完全错误的,类似于HTTP/1.1,而HTTP/2格式完全不同并且基于HPACK。

我建议您在编写进一步的代码之前仔细阅读HTTP/2规范。

最新更新