【问题标题】:Send HTTP2 frame to server over TLS通过 TLS 向服务器发送 HTTP2 帧
【发布时间】:2017-05-23 04:50:37
【问题描述】:

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

【问题讨论】:

  • PRI * HTTP/2.0\r\n\r\n (et al) - 通常有 一个 CRLF 分隔标题选项。最后一个选项得到 两个 CRLF。看起来您为每个选项连接了两个CRLF。 (我可能是错的,因为我从来没有做过你正在做的事情)。
  • @jww,OP的前言是正确的,由RFC 7540定义。

标签: c++ sockets ssl http2


【解决方案1】:

你有几个错误。

首先是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

【讨论】:

  • 我使用该结构(打包)来编写类型 = 0x4、标志 = 0 和 len = 0 的设置框架。工作但根本不知道如何编写“标题”框架(type = 5, flags = 0x5, len = 020, identifier = 0001) 然后有效载荷:"Accept":"application/json" 但不起作用。
  • @Brandon,HEADERS 框架需要使用HPACK 编写。
猜你喜欢
  • 1970-01-01
  • 2019-01-25
  • 1970-01-01
  • 2014-11-20
  • 2011-05-10
  • 1970-01-01
  • 2017-01-26
  • 2018-05-26
  • 1970-01-01
相关资源
最近更新 更多