A Guide to Getting Started With QUIC

proto-quicQUIC的独立库。 proto-quic包含了QUIC所需的Chromium代码和相关依赖。因此可以使用部分Chromium代码, 而不必依赖于完整的Chromium。 proto-quic旨在成为一个跨平台库,但目前只支持Chromium 已经支持的平台(或部分平台)。

QUIC并不是官方支持的Google产品。目前由一些QUIC开发人员作为一个编外计划保持更新 (理论上更新周期为一周)。最糟糕的情况下,如果Google的优先级改变,不再支持一个 独立的QUIC库。但proto-quic是完全开源的,任何感兴趣的社区可以克隆仓库并继续更新。

目前,唯一支持的平台是Linux(并且目前仅测试了Ubuntu版本),但Windows和iOS 应该 很快就会支持。

注意末尾带*的为必要步骤!!!

测试的proto-quic版本为:Updating to 56.0.2912.0 (#15)

$ git reset --hard 0db5f234b86699c182394bb261ee226ab800b60f
$ git log

commit 0db5f234b86699c182394bb261ee226ab800b60f
Author: fayang <[email protected]>
Date:   Tue Nov 8 18:44:44 2016 +0800

    Updating to 56.0.2912.0 (#15)

    * Updating to 56.0.2912.0

    * Updating to 56.0.2912.0

1 下载项目仓库并安装相关依赖*

# 下载项目仓库
$ git clone https://github.com/google/proto-quic.git

# 跳转至项目目录
$ cd proto-quic

# 保持在"Updating to 56.0.2912.0 (#15)"版本(目前测试版本)的原始状态,若想尝试最新版,则忽略此语句
$ git reset --hard 0db5f234b86699c182394bb261ee226ab800b60f

# 注意下面这步很重要,否则之后的操作会无法找到gn或ninja等命令,注意此命令设置的环境变量只对当前终端有效
$ export PATH=$PATH:`pwd`/depot_tools

# 安装相关依赖(可能会遇到各种问题,下面会作详细说明)
$ ./proto_quic_tools/sync.sh

安装相关依赖时需要下载Chrome OS fonts,binutils等,可能需要使用代理。

1.1 手动下载相关依赖

若无法直接通过使用代理并执行./proto_quic_tools/sync.sh下载相关依赖,如 Chrome OS fonts和binutils。则先手动下载并解压相关文件到相应位置,再执行 ./proto_quic_tools/sync.sh命令。

若已成功执行./proto_quic_tools/sync.sh,请跳过此小节。

1.1.1 手动安装Chrome OS fonts

chrome fonts的安装脚本位于proto-quic/src/build/linux/install-chromeos-fonts.py, 其主要代码(若不想看代码,可跳过,直接看之后的总结)如下。

#!/usr/bin/env python
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Script to install the Chrome OS fonts on Linux.
# This script can be run manually (as root), but is also run as part
# install-build-deps.sh.

import os
import shutil
import subprocess
import sys

URL_TEMPLATE = ('https://commondatastorage.googleapis.com/chromeos-localmirror/'
                'distfiles/%(name)s-%(version)s.tar.bz2')

# Taken from the media-fonts/<name> ebuilds in chromiumos-overlay.
SOURCES = [
  {
    'name': 'notofonts',
    'version': '20160310'
  }, {
    'name': 'robotofonts',
    'version': '2.132'
  }
]

## 此时可以知道,URLS为一个含两个字符串元素的数组
## URLS为['https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/notofonts-20160310.tar.bz2',
##       'https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/robotofonts-2.132.tar.bz2']
URLS = sorted([URL_TEMPLATE % d for d in SOURCES])



FONTS_DIR = '/usr/local/share/fonts'

def main(args):
  if not sys.platform.startswith('linux'):
    print "Error: %s must be run on Linux." % __file__
    return 1

  if os.getuid() != 0:
    print "Error: %s must be run as root." % __file__
    return 1

  if not os.path.isdir(FONTS_DIR):
    print "Error: Destination directory does not exist: %s" % FONTS_DIR
    return 1

  ## 此时可知,dest_dir为'/usr/local/share/fonts/chromeos'
  dest_dir = os.path.join(FONTS_DIR, 'chromeos')

  ## 此时可知,stamp为'/usr/local/share/fonts/chromeos/.stamp02'
  stamp = os.path.join(dest_dir, ".stamp02")

  ## 此时可知,下载程序判断的依据为:存在stamp,且stamp的字符串内容与URLS数组用'\n'连接而成的字符串的内容相同
  ## 注意还需将tarball的内容解压到tarball所在的文件夹中
  if os.path.exists(stamp):
    with open(stamp) as s:
      if s.read() == '\n'.join(URLS):
        ## 若下载程序就此退出,表明安装完chrome os fonts
        print "Chrome OS fonts already up to date in %s." % dest_dir
        return 0

## 若已手动完成了之后程序的内容,则设法使程序在此终止
## ------------------------------------------------------------------------
  if os.path.isdir(dest_dir):
    shutil.rmtree(dest_dir)
  os.mkdir(dest_dir)
  os.chmod(dest_dir, 0755)

  print "Installing Chrome OS fonts to %s." % dest_dir
  ## 循环取URLS数组中的url,URLS为['https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/notofonts-20160310.tar.bz2',
  ## 'https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/robotofonts-2.132.tar.bz2']
  for url in URLS:
    ## 此时可知,tarball为'/usr/local/share/fonts/chromeos/notofonts-20160310.tar.bz2'
    ##                 和'/usr/local/share/fonts/chromeos/robotofonts-2.132.tar.bz2'
    tarball = os.path.join(dest_dir, os.path.basename(url))
    ## 下载tarball压缩包 (必要步骤 1)
    subprocess.check_call(['curl', '-L', url, '-o', tarball])
    ## 解压tarball (必要步骤 2)
    subprocess.check_call(['tar', '--no-same-owner', '--no-same-permissions',
                           '-xf', tarball, '-C', dest_dir])
    ## 解压后,就可以删除tarball压缩包
    os.remove(tarball)

  ## 由此可知,readme为'/usr/local/share/fonts/chromeos/README'
  readme = os.path.join(dest_dir, "README")
  ## 写README文件
  with open(readme, 'w') as s:
    s.write("This directory and its contents are auto-generated.\n")
    s.write("It may be deleted and recreated. Do not modify.\n")
    ## __file__为'./src/build/linux/install-chromeos-fonts.py'
    s.write("Script: %s\n" % __file__)

  ## 写stamp文件 (必要步骤 3) ,stamp为'/usr/local/share/fonts/chromeos/.stamp02'
  with open(stamp, 'w') as s:
    s.write('\n'.join(URLS))

  ## 将所有的文件夹的权限设置为755,所有文件的权限设置为644
  for base, dirs, files in os.walk(dest_dir):
    for dir in dirs:
      os.chmod(os.path.join(base, dir), 0755)
    for file in files:
      os.chmod(os.path.join(base, file), 0644)

  return 0

if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))

总结一下安装Chrome OS fonts所需的必要步骤:

必要步骤 1 - 下载tarball压缩包

下载下面两个文件到/usr/local/share/fonts/chromeos目录中。(注意:如下载地址有变化, 以proto-quic/src/build/linux/install-chromeos-fonts.py的内容为准)

['https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/notofonts-20160310.tar.bz2',
'https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/robotofonts-2.132.tar.bz2']
# 可用浏览器下载(推荐)到/usr/local/share/fonts/chromeos目录中,或者在终端中wget下载
$ wget --no-check-certificate https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/notofonts-20160310.tar.bz2 -O /usr/local/share/fonts/chromeos/notofonts-20160310.tar.bz2
$ wget --no-check-certificate https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/robotofonts-2.132.tar.bz2 -O /usr/local/share/fonts/chromeos/robotofonts-2.132.tar.bz2
必要步骤 2 - 解压tarball
$ tar -vxf --no-same-owner --no-same-permissions /usr/local/share/fonts/chromeos/notofonts-20160310.tar.bz2 -C /usr/local/share/fonts/chromeos/
$ tar -vxf --no-same-owner --no-same-permissions /usr/local/share/fonts/chromeos/robotofonts-2.132.tar.bz2 -C /usr/local/share/fonts/chromeos/
必要步骤 3 - 写stamp文件
$ sudo python [进入python终端后输入如下内容]
>>> URLS = ['https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/notofonts-20160310.tar.bz2',
           'https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/robotofonts-2.132.tar.bz2']
>>> stamp = '/usr/local/share/fonts/chromeos/.stamp02'
>>> with open(stamp, 'w') as s:
      s.write('\n'.join(URLS))

>>> exit()  [退出python终端]

此时,Chrome OS fonts的必要安装步骤就完成了,install-chromeos-fonts.py文件中还 删除了解压后的tarball压缩包,并在同一目录下生成一个README文件。

1.1.2 手动安装binutils

binutils的安装脚本位于proto-quic/src/third_party/binutils/download.py, 其主要代码(若不想看代码,可跳过,直接看之后的总结)如下。

#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# vim: set ts=2 sw=2 et sts=2 ai:

"""Minimal tool to download binutils from Google storage.

TODO(mithro): Replace with generic download_and_extract tool.
"""

import argparse
import os
import platform
import re
import shutil
import subprocess
import sys


BINUTILS_DIR = os.path.abspath(os.path.dirname(__file__))
BINUTILS_FILE = 'binutils.tar.bz2'
BINUTILS_TOOLS = ['bin/ld.gold', 'bin/objcopy', 'bin/objdump']
BINUTILS_OUT = 'Release'

DETECT_HOST_ARCH = os.path.abspath(os.path.join(
    BINUTILS_DIR, '../../build/detect_host_arch.py'))


def ReadFile(filename):
  with file(filename, 'r') as f:
    return f.read().strip()


def WriteFile(filename, content):
  assert not os.path.exists(filename)
  with file(filename, 'w') as f:
    f.write(content)
    f.write('\n')


def GetArch():
  gyp_host_arch = re.search(
      'host_arch=(\S*)', os.environ.get('GYP_DEFINES', ''))
  if gyp_host_arch:
    arch = gyp_host_arch.group(1)
    # This matches detect_host_arch.py.
    if arch == 'x86_64':
      return 'x64'
    return arch

  return subprocess.check_output(['python', DETECT_HOST_ARCH]).strip()

## 关键的函数,获取并解压binutils.tar.bz2文件
def FetchAndExtract(arch):
  ## 由此可知,archdir为'<base path>/proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)'
  ## arch可由终端下的命令arch得出,若显示x86_64则为x64,否则为ia32
  archdir = os.path.join(BINUTILS_DIR, 'Linux_' + arch)

  ## 由此可知,tarball为'<base path>/proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)/binutils.tar.bz2'
  tarball = os.path.join(archdir, BINUTILS_FILE)

  ## 由此可知,outdir为'<base path>/proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)/Release'(注意区分大小写,Release的首字母大写)
  outdir = os.path.join(archdir, BINUTILS_OUT)

  ## 由此可知,sha1file为'<base path>/proto-quic/src/third_party/binutils/Linux_ia32(或x64)/binutils.tar.bz2.sha1'
  sha1file = tarball + '.sha1'
  if not os.path.exists(sha1file):
    print "WARNING: No binutils found for your architecture (%s)!" % arch
    return 0

  ## 由此可知,checksum为sha1file的内容,即所下载的tarball的文件的sha1校验码,以确定所下载文件为所需文件
  checksum = ReadFile(sha1file)

  ## 由此可知,stampfile为'<base path>/proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)/binutils.tar.bz2.stamp'
  stampfile = tarball + '.stamp'

  ## 此时可知,下载程序判断的依据为:存在stampfile,存在tarball,存在outdir,且stampfile的字符串内容与sha1file的字符串内容相同
  ## 注意还需将tarball的内容解压到outdir中,即outdir文件夹中的结构为bin、include和lib。
  if os.path.exists(stampfile):
    if (os.path.exists(tarball) and
        os.path.exists(outdir) and
        checksum == ReadFile(stampfile)):
      ## 若下载程序就此退出,表明安装完binutls
      return 0
    else:
      os.unlink(stampfile)

## 若已手动完成了之后程序的内容,则设法使程序在此终止
## ------------------------------------------------------------------------

  ## 此处隐藏了真实的下载地址,调用了proto-quic/depot_tools/download_from_google_storage.py进行下载
  ## download_from_google_storage下载地址为gs://<bucket>/<sha1>
  ## 此处的bucket参数为chromium-binutils,故下载地址为gs://chromium-binutils/<sha1file文件存放的sha1>
  ## 例如,CPU为x86_64架构,且'<base path>/proto-quic/src/third_party/binutils/Linux_x64'目录下的sha1file的内容为d9064388bed0e7225b1366d80b59289b1509d7c2
  ## (若CPU为ia32架构,则选取'<base path>/proto-quic/src/third_party/binutils/Linux_ia32'目录下sha1file的内容)
  ## arch可由终端下的命令arch得出,若显示x86_64则为x64,否则为ia32
  ## 并将'gs://'替换为'https://storage.googleapis.com/'
  ## 最终,组合而成的地址https://storage.googleapis.com/chromium-binutils/d9064388bed0e7225b1366d80b59289b1509d7c2  (x64架构,推荐,因ia32架构的如果下载不到,程序会选择下载x64架构的)
  ## (或者 https://storage.googleapis.com/chromium-binutils/<'.../binutils/Linux_ia32'目录下sha1file的内容>  (ia32架构) )

  ## 下载tarball文件 (必要步骤 1),tarball为'<base path>/proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)/binutils.tar.bz2'
  print "Downloading", tarball
  subprocess.check_call([
      'download_from_google_storage',
      '--no_resume',
      '--no_auth',
      '--bucket', 'chromium-binutils',
      '-s', sha1file])
  assert os.path.exists(tarball)

  ## 删除并新建outdir文件夹 (必要步骤 2),outdir为'<base path>/proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)/Release/'(注意区分大小写,Release的首字母大写)
  if os.path.exists(outdir):
    shutil.rmtree(outdir)
  assert not os.path.exists(outdir)
  os.makedirs(outdir)
  assert os.path.exists(outdir)

  ## 解压tarball文件到outdir文件夹中 (必要步骤 3)
  print "Extracting", tarball
  subprocess.check_call(['tar', 'axf', tarball], cwd=outdir)

  ## 确认outdir文件夹下,含有所有BINUTILS_TOOLS数组所列举的文件
  ## 其中,BINUTILS_TOOLS为['bin/ld.gold', 'bin/objcopy', 'bin/objdump']
  for tool in BINUTILS_TOOLS:
    assert os.path.exists(os.path.join(outdir, tool))

  ## 将sha1file的字符串内容写入到stampfile中 (必要步骤 4)
  WriteFile(stampfile, checksum)
  return 0


def main(args):
  parser = argparse.ArgumentParser(description=__doc__)
  parser.add_argument('--ignore-if-arch', metavar='ARCH',
                      action='append', default=[],
                      help='Do nothing on host architecture ARCH')

  options = parser.parse_args(args)

  if not sys.platform.startswith('linux'):
    return 0

  arch = GetArch()
  if arch in options.ignore_if_arch:
    return 0

  ## 执行FetchAndExtract函数,下载并解压binutil文件
  if arch == 'x64':
    return FetchAndExtract(arch)
  if arch == 'ia32':
    ret = FetchAndExtract(arch)
    if ret != 0:
      return ret
    # Fetch the x64 toolchain as well for official bots with 64-bit kernels.
    return FetchAndExtract('x64')

  print "Host architecture %s is not supported." % arch
  return 1


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))

总结一下安装binutils所需的必要步骤:

必要步骤 1 - 下载tarball压缩包

查看计算机的CPU架构

# 若显示的是x86_64,则CPU架构为x64,若显示ia32,则CPU架构为ia32
$ arch

以下的内容以x64为例,即目标文件夹为proto-quic/src/third_party/binutils/Linux_x64/ (若为ia32,则目标文件夹为proto-quic/src/third_party/binutils/Linux_ia32/, 注意修改下列的相关内容)。

x64架构的下载地址为https://storage.googleapis.com/chromium-binutils/d9064388bed0e7225b1366d80b59289b1509d7c2 (若为ia32架构,则下载地址为https://storage.googleapis.com/chromium-binutils/24f937cfdad77bdcd6ad8cacc542d806f3eb4b0f, 最后的字符串为目标文件夹下的binutils.tar.bz2.sha1文件的内容,注意修改下列的相关内容)

(注意: 如下载地址有变化,以proto-quic/src/third_party/binutils/download.py的内容为准)

下载binutil文件到proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)/ 目录中,并命名为binutils.tar.bz2。以x64架构为例。

# 可用浏览器下载(推荐)到proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)目录中,或者在终端中wget下载
$ cd proto-quic/src/third_party/binutils/Linux_x64   (x64架构)
(ia32架构的为: cd proto-quic/src/third_party/binutils/Linux_ia32)

$ wget --no-check-certificate https://storage.googleapis.com/chromium-binutils/d9064388bed0e7225b1366d80b59289b1509d7c2 -O ./binutils.tar.bz2  (x64架构)
(ia32架构的为: wget --no-check-certificate https://storage.googleapis.com/chromium-binutils/24f937cfdad77bdcd6ad8cacc542d806f3eb4b0f -O ./binutils.tar.bz2)
必要步骤 2 - 删除并新建outdir文件夹

outdir为proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)/Release/ (注意区分大小写,Release的首字母大写)

# 先切换到[proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)文件夹中]
$ rm -R Release
$ mkdir Release
必要步骤 3 - 解压tarball文件到outdir文件夹中
# 先切换到[proto-quic/src/third_party/binutils/Linux_ia32(或Linux_x64)文件夹中]
$ tar -vaxf ./binutils.tar.bz2 -C ./Release/
必要步骤 4 - 将sha1file的字符串内容写入到stampfile中
$ sudo python [进入python终端后输入如下内容]
>>> sha1file = '/home/vmu/proto-quic/src/third_party/binutils/Linux_x64/binutils.tar.bz2.sha1' (vmu为系统的用户名,需要自行替换,绝对路径)
(ia32架构的为 stampfile = '/home/vmu/proto-quic/src/third_party/binutils/Linux_ia32/binutils.tar.bz2.sha1' (vmu为系统的用户名,需要自行替换,绝对路径))

>>> stampfile = '/home/vmu/proto-quic/src/third_party/binutils/Linux_x64/binutils.tar.bz2.stamp'  (vmu为系统的用户名,需要自行替换,绝对路径)
(ia32架构的为 stampfile = '/home/vmu/proto-quic/src/third_party/binutils/Linux_ia32/binutils.tar.bz2.stamp'  (vmu为系统的用户名,需要自行替换,绝对路径))

>>> checksum = None
>>> with file(sha1file, 'r') as f:
      checksum = f.read().strip()

>>> with file(stampfile, 'w') as f:
      f.write(checksum)
      f.write('\n')

>>> exit() [退出python终端]

此时,binutils的安装步骤就完成了。

然后,记得在处于proto-quic目录的终端下,再次执行./proto_quic_tools/sync.sh命令。

成功时会出现:

No missing packages, and the packages are up to date.

Installing Chrome OS fonts.
Installing Chrome OS fonts to /usr/local/share/fonts/chromeos.
  % Total % Received % Xferd Average Speed Time Time Time Current
                                                   Dload Upload Total Spent Left Speed
100 11.7M 100 11.7M 0 0 210k 0 0:00:57 0:00:57 --:--:-- 233k
  % Total % Received % Xferd Average Speed Time Time Time Current
                                                   Dload Upload Total Spent Left Speed
100 2811k 100 2811k 0 0 434k 0 0:00:06 0:00:06 --:--:-- 520k
Installing symbolic links for NaCl.
Creating link: /usr/lib/i386-linux-gnu/libcrypto.so
Creating link: /usr/lib/i386-linux-gnu/libssl.so
Downloading /home/vmu/proto-quic/src/third_party/binutils/Linux_x64/binutils.tar.bz2
NOTICE: You have PROXY values set in your environment, but gsutil in depot_tools does not (yet) obey them.
Also, --no_auth prevents the normal BOTO_CONFIG environment variable from being used.
To use a proxy in this situation, please supply those settings in a .boto file pointed to by the NO_AUTH_BOTO_CONFIG environment var.
0> Downloading /home/vmu/proto-quic/src/third_party/binutils/Linux_x64/binutils.tar.bz2...
Success!
Downloading 1 files took 61.413792 second(s)
Extracting /home/vmu/proto-quic/src/third_party/binutils/Linux_x64/binutils.tar.bz2
done.

2 生成QUIC的client,server和tests*

# 由proto-quic进入proto-quic/src目录
$ cd src

# 编译并生成quic_client,quic_server和net_unittests
$ gn gen out/Debug && ninja -C out/Debug quic_client quic_server net_unittests
(或者(如未按第一步添加环境变量) ../depot_tools/gn gen out/Debug && ../depot_tools/ninja -C out/Debug quic_client quic_server net_unittests
[gn位于proto-quic/depot_tools,输出目录为out/Default或out/Debug等均可,名字无要求,后文中为out/Debug])

成功时会出现:

ninja: Entering directory `out/Default'
[2785/2785] LINK ./quic_client

此时QUIC所依赖的环境已经搭建完成, 转入下面的Playing with QUIC部分。


Playing with QUIC

除了使用proto-quic,还可以使用完整的Chromium环境,并类似proto-quic的使用方法, 生成quic_serverquic_client二进制文件。


3 准备测试数据,网页信息来自www.example.org*

3.1 下载www.example.org网页(包括HTTP头部), 将由quic_server来维护*

# 在用户的家目录新建quic-data目录,此时的$HOME目录为'/home/<user name>',注意若以root用户运行,其$HOME目录为'/'根目录
$ mkdir $HOME/quic-data

# 进入quic-data目录中
$ cd $HOME/quic-data

# 下载https://www.example.org网页(包括HTTP头部)
$ wget -p --save-headers https://www.example.org

# 回到原来的目录proto-quic中
$ cd -

3.2 手动编辑index.html,修改HTTP头部*

# 手动编辑index.html,修改HTTP头部,注意此时的$HOME目录为'/home/<user name>',不要用root权限运行,否则其$HOME目录为'/'根目录
$ vi $HOME/quic-data/www.example.org/index.html

index.html的HTTP头部为<!doctype html>标签的上部分内容,与<!doctype html>标签要留有一行空行。
其他文件的HTTP头部为与正文相隔一行空行的顶部部分。
HTTP头部的编辑方法如下,
- 移除 (如果有的话): "Transfer-Encoding: chunked"
- 移除 (如果有的话): "Alternate-Protocol: ..."
- 增加 (如果第一行不是的话): HTTP/1.1 200 OK [必须加上,否则客户端访问时会出现500错误]
- 添加 (如果没有的话): X-Original-Url: https://www.example.org/index.html [必须加上,否则客户端访问时会出现404错误]

即index.html的内容至少为以下的格式:
(实际上www.example.org文件夹内的所有文件(包括隐藏文件)的内容也至少为以下格式,
X-Original-Url的路径根据相应文件的具体的路径和文件名而定,
否则之后运行服务器程序时会因无法取出HTTP响应头部信息出错)
HTTP/1.1 200 OK^M
X-Original-Url: https://www.example.org/index.html^M
^M     [一个空行划分HTTP头部和正文]
<!doctype html>...,如果是二进制文件或普通文本,则为二进制内容或普通的文本内容
(其中^M表示Windows的CR/LF换行,在vi中在编辑模式下可以用"ctrl+v, ctrl+m"输入,而非普通的可见字符。)

4 生成证书*

为了运行服务器,需要有效的证书和pkcs8格式的私钥。 如果没有,可以使用脚本来生成。

4.1 在服务器端生成HTTPS服务器证书[proto-quic/src目录下]*

# 由proto-quic/src进入net/tools/quic/certs目录
$ cd net/tools/quic/certs

# 运行脚本,生成pem格式的证书和pkcs8格式的私钥
$ ./generate-certs.sh
(注意脚本中生成证书的有效期为三天,可以修改脚本,搜索days,将其后的参数3改为365或其他更大的数字,这样就不用时常更新证书了)

除了服务器的证书和公钥,此脚本还将生成CA证书(net/tools/quic/certs/out/2048-sha256-root.pem) 您需要将其添加到操作系统的根证书存储,以便在证书验证期间使其可信。 为了在linux上这样做,请参阅这些说明

4.2 在客户端中添加HTTPS服务器证书[proto-quic/src/net/tools/quic/certs目录下]*

# 安装管理证书的工具(选择符合自己Linux发行版的其中一行即可):
- Debian/Ubuntu: $ sudo apt-get install libnss3-tools
- Fedora: su -c "yum install nss-tools"
- Gentoo: su -c "echo 'dev-libs/nss utils' >> /etc/portage/package.use && emerge dev-libs/nss" (You need to launch all commands below with the nss prefix, e.g., nsscertutil.)
- Opensuse: sudo zypper install mozilla-nss-tools

# 添加证书(证书类型-自签证书),注意此时的$HOME目录为'/home/<user name>',不要用root权限运行,否则其$HOME目录为'/'根目录:
$ certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n quic-server -i out/leaf_cert.pem

# 查看客户端中所添加的证书,检查一下是否添加成功:
$ certutil -d sql:$HOME/.pki/nssdb -L

# 删除证书,慎用,初次添加证书时请忽略。(除非再次添加相同名字的证书时,需要先删除原先添加的证书):
$ certutil -d sql:$HOME/.pki/nssdb -D -n quic-server

# 返回之前的目录:
$ cd -

注意:
如果出现certutil: function failed: security library: bad database错误,
则删除证书数据库,再重建
$ mkdir -p ~/.pki/nssdb
$ sudo chmod 775 ~/.pki/nssdb
$ certutil -d ~/.pki/nssdb -N
如需要设置数据库的密码时,输入两个回车即可。

如果出现the certificate /key database is in an old ,unsupported format错误,则去掉前缀"sql:",即
$ certutil -d $HOME/.pki/nssdb -A -t "P,," -n quic-server -i out/leaf_cert.pem
数据库类型从flat files到Berkeley DB,最后到sqllite, 所以最新版的话sql:<DIRECTORY LEVEL PATH OF DATABASE>是必须的,如果显示格式不支持,则去掉前缀"sql:"。
当然,同样可以新建数据库,这样就可以保证使用的是sqllite格式的。

5 运行QUIC服务器和客户端*

5.1 运行quic_server服务器 [proto-quic/src目录下]*

# 运行quic_server架设网站,此时的$HOME目录为'/home/<user name>',注意若以root用户运行,其$HOME目录为'/'根目录
$ ./out/Debug/quic_server --quic_in_memory_cache_dir=$HOME/quic-data/www.example.org --certificate_file=net/tools/quic/certs/out/leaf_cert.pem --key_file=net/tools/quic/certs/out/leaf_cert.pkcs8  --port=6121 --v=1(如果需要显示更多的log信息,则使用该参数)

注意:./out/Debug/quic_server是服务器程序,后面的均为参数。

如果程序立即停止运行而不是处于Listening状态,则需要根据--v=1显示的信息来进行调整。
重点检查1: www.example.org文件夹内的所有文件(包括隐藏文件)的内容必须保持3.2所提格式。
#查看www.example.org文件夹内所有文件的命令:
$ ls -a /home/<user name>/quic-data/www.example.org/

quic_server程序的其他可选参数:

$./out/Debug/quic_server -h
Usage: quic_server [options]

Options:
-h, --help show this help message and exit
--port=<port> specify the port to listen on
--quic_in_memory_cache_dir directory containing response data
  to load
--certificate_file=<file> path to the certificate chain
--key_file=<file> path to the pkcs8 private key
--v=1 show verbose which level equals to 1

5.2 使用quic_client客户端,通过QUIC请求文件[proto-quic/src目录下]*

$ ./out/Debug/quic_client --host=127.0.0.1 --port=6121 https://www.example.org/index.html --v=1(如果需要显示更多的信息,则使用该参数) --disable-certificate-verification(如果在客户端没有添加服务器根证书的信任,则使用该参数)  

注意:./out/Debug/quic_client是客户端程序,后面的均为参数。
如果让服务器的端口默认为6121,您必须指定客户端端口,因为它默认为80。 此外,如果您的本地计算机有多个环回地址(如果使用IPv4和IPv6),您必须选择一个特定的地址。

如果仍然有错误如404或500,则需要根据--v=1显示的信息来进行调整。
重点检查1: quic_client请求的文件https://www.example.org/index.html,必须与在服务器目录中的文件index.html中的HTTP头部 X-Original-Url: https://www.example.org/index.html^M 相对应
(其中^M表示Windows的CR/LF换行,在vi中在编辑模式下可以用"ctrl+v, ctrl+m"输入,而非普通的可见字符。)
重点检查2:  服务器是否生成了证书,客户端是否添加了相应证书
重点检查3:  服务器目录中的文件index.html中的HTTP头部的第一行必须为HTTP/1.1 200 OK^M,且HTTP头部与正文之间必须相隔一个空行。

quic_client程序的其他可选参数:

$./out/Debug/quic_client -h
Usage: quic_client [options] <url>

<url> with scheme must be provided (e.g. http://www.google.com)

Options:
-h, --help show this help message and exit
--host=<host> specify the IP address of the hostname to connect to
--port=<port> specify the port to connect to
--body=<body> specify the body to post
--body_hex=<body_hex> specify the body_hex to be printed out
--headers=<headers> specify a semicolon separated list of key:value pairs to add to request headers
--quiet specify for a quieter output experience
--quic-version=<quic version> specify QUIC version to speak
--version_mismatch_ok if specified a version mismatch in the handshake is not considered a failure
--redirect_is_success if specified an HTTP response code of 3xx is considered to be a successful response, otherwise a failure
--initial_mtu=<initial_mtu> specify the initial MTU of the connection
--disable-certificate-verification do not verify certificates
--v=1 show verbose which level equals to 1

特别说明 - 如果传输其他文件:

如果需要传输视频、图像、文本等任何其他文件(包括index.html),则需要在该文件的头部加上Http头部。
例如添加了bunny_480_100kbit_dash.mp4到quic-data/www.example.org目录下。

注意:www.example.org文件夹内的所有文件(包括隐藏文件)的内容也必须保持以下格式,
否则之后运行服务器程序时可能因无法取出HTTP响应头部信息出错。
#查看www.example.org文件夹内所有文件的命令:
$ ls -a /home/<user name>/quic-data/www.example.org/

# 注意保证 文件夹 的默认权限为775,非可执行文件 的默认权限为664。
$ sudo chmod 775 /home/<user name>/quic-data/www.example.org/
$ sudo chmod 664 /home/<user name>/quic-data/www.example.org/bunny_480_100kbit_dash.mp4

-------------------编辑 bunny_480_100kbit_dash.mp4 文件----------------------
HTTP/1.1 200 OK^M
X-Original-Url: https://www.example.org/bunny_480_100kbit_dash.mp4^M
^M
..................(文件正文二进制内容)
------------------------------End-------------------------------------------

注意Http的头部(Head)与正文(Body)需要相隔一个空行,其中上述两个头部内容是必须的,当然还可以添加其他的Http头部信息。
其中^M表示Windows的CR/LF换行,在vi中在编辑模式下可以用"ctrl+v, ctrl+m"输入,而非普通的可见字符。
注意Http头部中不能含有"Transfer-Encoding: chunked""Alternate-Protocol: ..."# 服务器端不需要变动[proto-quic/src目录下]:
./out/Debug/quic_server --quic_in_memory_cache_dir=$HOME/quic-data/www.example.org --certificate_file=net/tools/quic/certs/out/leaf_cert.pem --key_file=net/tools/quic/certs/out/leaf_cert.pkcs8 --port=6121 --v=1

# 客户端则需要改变请求的URL地址[proto-quic/src目录下]:
./out/Debug/quic_client --host=127.0.0.1 --port=6121 https://www.example.org/bunny_480_100kbit_dash.mp4 --disable-certificate-verification --v=1

# 或者使用google-chrome浏览器访问 (建议先在浏览器设置中清空浏览器缓存):
google-chrome --user-data-dir=/tmp/chrome-profile --no-proxy-server --enable-quic --origin-to-force-quic-on=www.example.org:443 --host-resolver-rules='MAP www.example.org:443 127.0.0.1:6121' https://www.example.org/bunny_480_100kbit_dash.mp4

如果出现以下认证问题,则请按4.2删除证书后,再次添加证书。或者在client命令后后加上 --disable-certificate-verification参数。

[1109/061201:ERROR:cert_verify_proc_nss.cc(942)] CERT_PKIXVerifyCert for www.example.org failed err=-8179
[1109/061201:WARNING:proof_verifier_chromium.cc(466)] Failed to verify certificate chain: net::ERR_CERT_AUTHORITY_INVALID
Failed to connect to 127.0.0.1:6121. Error: QUIC_PROOF_INVALID

成功时会显示:

Connected to 127.0.0.1:6121
Request:
headers:
{
  :method:GET
  :authority:www.example.org
  :scheme:https
  :path:/index.html
}
body:

Response:
headers:
{
  :status:200
  cache-control:max-age=604800
  content-type:text/html
  date:Wed, 09 Nov 2016 12:50:08 GMT
  etag:"359670651+ident"
  expires:Wed, 16 Nov 2016 12:50:08 GMT
  last-modified:Fri, 09 Aug 2013 23:54:35 GMT
  server:ECS (cpm/F845)
  vary:Accept-Encoding
  x-cache:HIT
  x-ec-custom-error:1
  content-length:1270
  x-original-url:https://www.example.org/index.html
}

body: <!doctype html>
<html>
<head>
  <title>Example Domain</title>

  <meta charset="utf-8" />
  <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <style type="text/css">
  body {
  background-color: #f0f0f2;
  margin: 0;
  padding: 0;
  font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

  }
  div {
  width: 600px;
  margin: 5em auto;
  padding: 50px;
  background-color: #fff;
  border-radius: 1em;
  }
  a:link, a:visited {
  color: #38488f;
  text-decoration: none;
  }
  @media (max-width: 700px) {
  body {
  background-color: #fff;
  }
  div {
  width: auto;
  margin: 0 auto;
  border-radius: 0;
  padding: 1em;
  }
  }
  </style>
</head>

<body>
<div>
  <h1>Example Domain</h1>
  <p>This domain is established to be used for illustrative examples in documents. You may use this
  domain in examples without prior coordination or asking for permission.</p>
  <p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

Request succeeded (200).

5.3 也可以使用chrome浏览器作为客户端来请求文件

Ubuntu 64bit的google-chrome浏览器下载地址: google-chrome-stable_current_amd64.deb

下载后,使用dpkg进行安装。

# 安装chrome浏览器
$ dpkg -i google-chrome-stable_current_amd64.deb

接着,使用使用chrome请求文件。

$ google-chrome \
  --user-data-dir=/tmp/chrome-profile \
  --no-proxy-server \
  --enable-quic \
  --origin-to-force-quic-on=www.example.org:443 \
  --host-resolver-rules='MAP www.example.org:443 127.0.0.1:6121' \
  https://www.example.org/index.html

注意:可能浏览器程序的名字是chrome,而不是google-chrome.
浏览器Host绑定www.example.org:443 到 127.0.0.1:6121。
不能直接访问 127.0.0.1:6121 的原因是认证的问题,根证书添加的对应的host名是example.org,
所以浏览器访问的是 https://www.example.org/index.html 。

注意:如果改动了网页的内容,然后浏览器访问没有变化,则可能是浏览器缓存的问题。可以到浏览器里清空缓存(Clear browsing data - Cached images and files)

参考文献


The End.

zhlinh

Email: [email protected]

2016-12-30