• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • mod_proxy_ajp

    描述:mod_proxy的 AJP 支持模块
    状态:延期
    模块标识符:proxy_ajp_module
    源文件:mod_proxy_ajp.c
    兼容性:可在 version 2.1 及更高版本中使用

    摘要

    该模块需要mod_proxy的服务。它为Apache JServ Protocol version 1.3(以下简称 AJP13)提供支持。

    因此,为了获得处理AJP13协议的能力,必须在服务器中存在mod_proxy和mod_proxy_ajp。

    警告

    在有保护你的服务器之前不要启用代理。开放代理服务器对您的网络和整个 Internet 都是危险的。

    用法

    此模块用于使用 AJP13 协议将代理反向代理到后端 application 服务器(e.g. Apache Tomcat)。用法类似于 HTTP 反向代理,但使用ajp://前缀:

    简单的反向代理

    ProxyPass "/app" "ajp://backend.example.com:8009/app"
    

    也可以使用平衡器:

    平衡器反向代理

    <Proxy "balancer://cluster">
        BalancerMember "ajp://app1.example.com:8009" loadfactor=1
        BalancerMember "ajp://app2.example.com:8009" loadfactor=2
        ProxySet lbmethod=bytraffic
    </Proxy>
    ProxyPass "/app" "balancer://cluster/app"
    

    请注意,通常不需要ProxyPassReverse指令。 AJP 请求包括给代理的原始 host 头,并且 application 服务器可以生成相对于此 host 的 self-referential headers,因此不需要重写。

    主要的 exception 是代理上的 URL 路径与后端上的 URL 路径不同。在这种情况下,对于 example,可以相对于原始 host URL(而不是后端ajp:// URL)重写重定向标头:

    重写代理路径

    ProxyPass "/apps/foo" "ajp://backend.example.com:8009/foo"
    ProxyPassReverse "/apps/foo" "http://www.example.com/foo"
    

    但是,通常最好将后端服务器上的 application 部署在与代理相同的路径上,而不是采用这种方法。

    环境变量

    名称前缀为AJP_的环境变量将作为 AJP 请求属性(从 key 的 name 中删除 AJP前缀)转发到源服务器。

    协议概述

    AJP13协议是 packet-oriented。由于 performance 的原因,可能会选择二进制格式而不是更易读的纯文本。 web 服务器通过 TCP 连接与 servlet 容器通信。为了减少 socket 创建的昂贵的 process,web 服务器将尝试维护到 servlet 容器的持久 TCP 连接,并重用多个 request/response 循环的连接。

    将连接分配给特定请求后,在 request-handling 周期终止之前,不会将其用于任何其他请求。换句话说,请求不是通过连接多路复用的。这使得连接的任何一端都有更简单的 code,尽管它确实会导致更多的连接一次打开。

    一旦 web 服务器打开了与 servlet 容器的连接,该连接就可以处于以下状态之一:


    • 没有通过此连接处理请求。
    • 分配
      连接正在处理特定请求。

    一旦分配了连接来处理特定请求,基本请求信息(e.g. HTTP headers 等)就会以高度压缩的形式通过连接发送(e.g. common strings 被编码为整数)。请求数据包结构中包含该格式的详细信息。如果请求(content-length > 0)有一个正文,那么紧接着就会在一个单独的数据包中发送。

    此时,servlet 容器可能已准备好开始处理请求。在这样做时,它可以将以下消息发送回 web 服务器:

    • SENDHEADERS
      将一组 headers 发送回浏览器。
    • SEND_BODY_CHUNK
      将一大块正文数据发送回浏览器。
    • GET_BODY_CHUNK
      如果尚未全部转移请求,请从请求中获取更多数据。这是必要的,因为数据包具有固定的最大大小,并且任意数量的数据可以包含在请求的主体中(对于上传的 files,对于 example)。(注意:这与 HTTP 分块传输无关)。
    • END_RESPONSE
      完成 request-handling 循环。

    每条消息都附有不同格式的数据包。有关详细信息,请参阅下面的响应包结构

    基本数据包结构

    这个协议有一些 XDR 遗产,但它有很多种方式(对于 example 没有 4 字节对齐)。

    AJP13 对所有数据类型使用网络字节 order。

    协议中有四种数据类型:字节,布尔值,整数和 strings。

    • 字节
      一个字节。
    • **_**布尔
      单个字节,1 = true,0 = false。使用其他 non-zero 值作为 true(i.e .C-style)可能在某些地方有效,但在其他地方则不然。
    • 整数
      数字范围为 0 到 2 ^ 16(32768)。首先以 high-order 字节存储在 2 个字节中。
    • 一个 variable-sized string(长度由 2 ^ 16 限制)。首先将长度编码为两个字节,然后是 string(包括终止' 0')。请注意,编码长度不包括尾部' 0'-它就像 strlen。这在 Java 方面令人困惑,其中充斥着奇怪的自动增量 statements 以跳过这些终结符。我相信这样做的原因是为了在读取 servlet 容器发回的 strings 时允许 C code 非常有效-使用终止的\ 0 字符,C code 可以将 references 传递到单个缓冲区,而无需复制。如果缺少\ 0,C code 将不得不在 order 中复制出来以获得 string 的概念。

    包大小

    根据 code 的大部分内容,最大数据包大小为8 * 1024 bytes(8K)。数据包的实际长度在标头中编码。

    包 Headers

    从服务器发送到容器的数据包以0x1234开头。从容器发送到服务器的数据包以AB开头(这是 A 的 ASCII code,后跟 B 的 ASCII code)。在前两个字节之后,有一个 integer(如上编码)和有效载荷的长度。虽然这可能表明最大有效载荷可能大到 2 ^ 16,但事实上,code _set 最大值为 8K。

    包格式(服务器->容器)
    字节01234...(n 3)
    内容0 x120 x34数据长度(n)数据
    包格式(容器->服务器)
    字节01234...(n 3)
    内容一个数据长度(n)数据

    对于大多数数据包,有效负载的第一个字节编码消息类型。 exception 用于从服务器发送到容器的请求主体数据包-它们与标准数据包头(0x1234然后是数据包的长度)一起发送,但之后没有任何前缀 code。

    web 服务器可以将以下消息发送到 servlet 容器:

    包的类型含义
    2转发请求使用以下数据开始 request-processing 循环
    7关掉web 服务器要求容器自行关闭。
    8web 服务器要求容器进行控制(安全登录阶段)。
    10CPingweb 服务器要求容器使用 CPong 快速响应。
    没有数据大小(2 个字节)和相应的正文数据。

    为了确保一些基本的安全性,如果请求来自托管它的同一台机器,容器将只实际执行Shutdown

    web 服务器在Forward Request之后立即发送第一个Data数据包。

    servlet 容器可以将以下类型的消息发送到 Web 服务器:

    包的类型含义
    3发送身体块将身体的一部分从 servlet 容器发送到 web 服务器(可能是在浏览器上)。
    4发送 Headers将响应 headers 从 servlet 容器发送到 web 服务器(可能是在浏览器上)。
    5结束回应标记响应的结束(因此 request-handling 循环)。
    6获取身体块如果尚未全部转移请求,请从请求中获取更多数据。
    9CPong 回复对 CPing 请求的回复

    上述每条消息都有不同的内部结构,详述如下。

    请求数据包结构

    对于从服务器到 Forward Request 类型的容器的消息:

    AJP13_FORWARD_REQUEST :=
        prefix_code      (byte) 0x02 = JK_AJP13_FORWARD_REQUEST
        method           (byte)
        protocol         (string)
        req_uri          (string)
        remote_addr      (string)
        remote_host      (string)
        server_name      (string)
        server_port      (integer)
        is_ssl           (boolean)
        num_headers      (integer)
        request_headers *(req_header_name req_header_value)
        attributes      *(attribut_name attribute_value)
        request_terminator (byte) OxFF
    

    request_headers具有以下结构:

    req_header_name :=
        sc_req_header_name | (string)  [see below for how this is parsed]
    
    sc_req_header_name := 0xA0xx (integer)
    
    req_header_value := (string)
    

    attributes是可选的,具有以下结构:

    attribute_name := sc_a_name | (sc_a_req_attribute string)
    
    attribute_value := (string)
    

    并非 all-important 标头是content-length,因为它确定容器是否立即查找另一个数据包。

    前向请求元素的详细描述

    请求前缀

    对于所有请求,这将是 2.有关其他前缀代码的详细信息,请参阅上文。

    方法

    HTTP 方法,编码为单个字节:

    命令 Name
    OPTIONS1
    得到2
    3
    POST4
    5
    删除6
    跟踪7
    PROPFIND8
    PROPPATCH9
    MKCOL10
    复制11
    移动12
    13
    开锁14
    ACL15
    报告16
    VERSION-CONTROL17
    报到18
    查看19
    取消签20
    搜索21
    MKWORKSPACE22
    UPDATE23
    标签24
    合并25
    BASELINE_CONTROL26
    MKACTIVITY27

    稍后 version 的 ajp13,将传输其他方法,即使它们不在此列表中。

    协议,req_uri,remote_addr,remotehost,servername,serverport,is_ssl

    这些都是 self-explanatory。这些都是必需的,并将针对每个请求发送。

    Headers

    request_headers的结构如下:首先,编码 headers num_headers的数量。然后,接下来是一系列头 name req_header_name/value req_header_value对。 Common 标题名称被编码为整数,以节省空间。如果头 name 不在基本_header 列表中,则它被正常编码(作为 string,具有前缀长度)。 common headers sc_req_header_name列表及其代码如下(均为 case-sensitive):

    名称Code value代码名称
    接受0 xA001SC_REQ_ACCEPT
    accept-charset0 xA002SC_REQ_ACCEPTCHARSET
    accept-encoding0 xA003SC_REQ_ACCEPT_ENCODING
    accept-language0 xA004SC_REQ_ACCEPT_LANGUAGE
    授权0 xA005SC_REQ_AUTHORIZATION
    连接0 xA006SC_REQ_CONNECTION
    content-type0 xA007SC_REQCONTENTTYPE
    content-length0 xA008SC_REQ_CONTENT_LENGTH
    曲奇饼0 xA009SC_REQCOOKIE
    COOKIE20 xA00ASC_REQCOOKIE2
    主办0 xA00BSC_REQHOST
    编译0 xA00CSC_REQ_PRAGMA
    引荐0 xA00DSC_REQ_REFERER
    user-agent0 xA00ESC_REQ_USER_AGENT

    读取此_code 的 Java code 抓取第一个 two-byte integer,如果它在最高有效字节中看到'0xA0',则它使用第二个字节中的 integer 作为头名称 array 的索引。如果第一个字节不是0xA0,则假定 two-byte integer 是 string 的长度,然后读入。

    这是假设没有标题名称的长度大于0x9FFF(==0xA000 - 1),这是完全合理的,尽管有点武断。

    注意:

    content-length标头非常重要。如果它存在且 non-zero,则容器假定请求具有正文(POST 请求,对于 example),并立即从输入流中读取单独的数据包以获取该正文。

    属性

    前缀为?(e.g.?context)的属性都是可选的。对于每个,都有一个字节 code 来表示属性的类型,然后是 value(string 或 integer)。它们可以在任何 order 中发送(尽管 C code 总是在下面列出的 order 中发送它们)。发送一个特殊的终止 code 以表示可选属性列表的结尾。字节代码列表是:

    信息Code Value类型 Value注意
    ?context0 x01-目前尚未实施
    ? servlet_path0 x02-目前尚未实施
    ? remote_user0 x03string
    ? auth_type0 x04string
    ? querystring0 x05string
    ? jvmroute0 x06string
    ? ssl_cert0 x07string
    ? ssl_cipher0 x08string
    ? sslsession0 x09string
    ? req_attribute0 x0AstringName(属性的 name 如下)
    ? sslkey_size0 x0B整数
    are_done0 xFF-request_terminator

    servlet_path当前不是由 C code 设置的,并且大多数 Java code 完全忽略了为这些字段发送的任何内容(如果在其中一个代码之后发送 string,则其中一些实际上会 break)。我不知道这是一个 bug 还是一个未实现的 feature 或者只是残留的 code,但它在连接的两端都没有。

    remote_userauth_type可能是指 HTTP-level 身份验证,并传达 remote 用户的用户名和用于建立其身份的身份验证类型(e.g. Basic,Digest)。

    query_stringssl_certssl_cipherssl_session指的是 HTTP 和 HTTPS 的相应部分。

    jvm_route用于支持粘性会话-在存在多个 load-balancing 服务器的情况下将用户的 sesson 与特定的 Tomcat 实例相关联。

    除了这个基本属性列表之外,还可以通过req_attribute code 0x0A发送任意数量的其他属性。在 code 的每个实例之后立即发送一对表示属性 name 和 value 的 strings。环境值通过此方法传递。

    最后,在发送了所有属性之后,将发送属性终止符0xFF。这既指示属性列表的末尾,也指示请求包的结束。

    响应数据包结构

    对于容器可以发送回服务器的消息。

    AJP13_SEND_BODY_CHUNK :=
      prefix_code   3
      chunk_length  (integer)
      chunk        *(byte)
      chunk_terminator (byte) Ox00
    
    AJP13_SEND_HEADERS :=
      prefix_code       4
      http_status_code  (integer)
      http_status_msg   (string)
      num_headers       (integer)
      response_headers *(res_header_name header_value)
    
    res_header_name :=
        sc_res_header_name | (string)   [see below for how this is parsed]
    
    sc_res_header_name := 0xA0 (byte)
    
    header_value := (string)
    
    AJP13_END_RESPONSE :=
      prefix_code       5
      reuse             (boolean)
    
    AJP13_GET_BODY_CHUNK :=
      prefix_code       6
      requested_length  (integer)
    

    细节:

    发送身体块

    块基本上是二进制数据,并直接发送回浏览器。

    发送 Headers

    状态 code 和消息是常见的 HTTP 事物(e.g.200OK)。响应头名称的编码方式与请求头名称相同。有关如何将代码与 strings 区分开的详细信息,请参阅上面的 header_encoding。
    common headers 的代码是:

    名称Code value
    Content-Type0 xA001
    Content-Language0 xA002
    Content-Length0 xA003
    日期0 xA004
    Last-Modified0 xA005
    地点0 xA006
    Set-Cookie0 xA007
    Set-Cookie20 xA008
    Servlet-Engine0 xA009
    状态0 xA00A
    WWW-Authenticate0 xA00B

    在 code 或 string 标头 name 之后,标头 value 立即被编码。

    结束回应

    表示此 request-handling 周期结束。如果reuse flag 是 true (anything other than 0 in the actual C code),则此 TCP 连接现在可用于处理新的传入请求。如果reuse是 false(==0),则应关闭连接。

    获取身体块

    容器要求来自请求的更多数据(如果主体太大而不适合发送的第一个数据包或请求被分块)。服务器将发回一个正文包,其中包含的数据量是request_length的最小值,最大发送正文大小(8186(8 Kbytes - 6)),以及实际从请求正文发送的字节数。
    如果正文中没有更多数据(i.e.servlet 容器正在尝试读取正文末尾),服务器将发回一个空数据包,这是一个有效负载长度为 0 的主体数据包。

    上篇:mod_proxy

    下篇:mod_proxy_balancer