|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
The original document was written by Dan Milstein, danmil@shore.net on December 2000. The present document is generated out of an xml file to allow a more easy integration in the Tomcat documentation. この文書の元となったのは、2000 年 12 月に Dan Milstein ( danmil@shore.net)によって書かれたものです。 現在公開されている文書は XML ファイルから生成され、 よりいっそう Tomcat の文書と統合しやすいものになっています。 This describes the Apache JServ Protocol version 1.3 (hereafter ajp13 ). There is, apparently, no current documentation of how the protocol works. This document is an attempt to remedy that, in order to make life easier for maintainers of JK, and for anyone who wants to port the protocol somewhere (into jakarta 4.x, for example). この文書では、Apache JServプロトコル バージョン1.3 (以後は ajp13 )について説明します。 現在、プロトコルの動作方法についての文書はまったく存在しません。 この文書は、mod_jkの保守をおこなう人たちや、プロトコルを他に(たとえば、Jakarta 4.xに)移植したい人たちがより楽に作業できるように、この問題を改善しようとする試みです。
I am not one of the designers of this protocol -- I believe that Gal Shachor was the original designer. Everything in this document is derived from the actual implementation I found in the tomcat 3.x code. I hope it is useful, but I can't make any grand claims to perfect accuracy. I also don't know why certain design decisions were made. Where I was able, I've offered some possible justifications for certain choices, but those are only my guesses. In general, the C code which Shachor wrote is very clean and comprehensible (if almost totally undocumented). I've cleaned up the Java code, and I think it's reasonably readable. 私は、このプロトコルの設計者の一人ではありません - Gal Shachorがオリジナルの設計者だったと思います。 この文書のすべての情報は、私がTomcat 3.xのコードで見つけた実際の実装から得ています。 この文書が役に立つものであればと思いますが、完全に正確かどうかについては保証できません。 また、設計方針の決定の理由についても私は知りません。 私ができることは、いくつかの(設計での)選択に対して可能性のある理由づけを行うことだけですが、それらは単なる推測にすぎません。 全般的に、Shachorが書いたC言語のコードは非常にきれいで、(ほとんど文書化されていないにせよ)理解しやすいものです。 Java言語のコードについては、私が書き直して、かなり読みやすくなったと思います。
According to email from Gal Shachor to the jakarta-dev mailing list, the original goals of JK (and thus ajp13 ) were to extend mod_jserv and ajp12 by (I am only including the goals which relate to communication between the web server and the servlet container):
Gal Shachorがjakarta-devメーリングリストに投稿した電子メールによると、 JK (と ajp13 )の最初の目標は、 mod_jserv と ajp12 を以下のように拡張することでした(私は、WebサーバとServletコンテナ間の通信に関連した目標を取り込んだだけです)。
The ajp13 protocol is packet-oriented. A binary format was presumably chosen over the more readable plain text for reasons of performance. The web server communicates with the servlet container over TCP connections. To cut down on the expensive process of socket creation, the web server will attempt to maintain persistent TCP connections to the servlet container, and to reuse a connection for multiple request/response cycles. ajp13 プロトコルは、パケット指向です。 読みやすいプレーンテキスト形式ではなく、バイナリ形式を選択したのは、おそらく性能上の理由からだと思われます。 Webサーバは、ServletコンテナとTCPコネクション上で通信します。 コストのかかるソケット作成プロセスを減らすために、WebサーバはServletコンテナに対して永続的にTCPコネクションを保持して、複数のリクエスト/レスポンスのサイクルでコネクションを再利用しようとします。 Once a connection is assigned to a particular request, it will not be used for any others until the request-handling cycle has terminated. In other words, requests are not multiplexed over connections. This makes for much simpler code at either end of the connection, although it does cause more connections to be open at once. いったんある特定のリクエストにコネクションを割り当てると、リクエスト処理サイクルが終了するまでは他に使用することはありません。 つまり、リクエストは、コネクション上で多重化されることはありません。 これによって、コネクションの両側のコードをより単純にできる反面、結果として、一度にオープンしているコネクション数が多くなってしまいます。 Once the web server has opened a connection to the servlet container, the connection can be in one of the following states: いったんWebサーバがServletコンテナに対するコネクションをオープンすると、コネクションは以下の状態のいずれかになります。
Once a connection is assigned to handle a particular request, the basic request informaton (e.g. HTTP headers, etc) is sent over the connection in a highly condensed form (e.g. common strings are encoded as integers). Details of that format are below in Request Packet Structure. If there is a body to the request (content-length > 0), that is sent in a separate packet immediately after. いったんコネクションがある特定のリクエストを処理するために割り当てられると、基本的なリクエスト情報 (たとえば、HTTPヘッダなど)は、コネクション上を高度に圧縮された形式(たとえば、一般的な文字列は整数にエンコードされます)で送信されます。 このフォーマットの詳細については、この後のリクエストパケット構造で説明します。 リクエストに対してボディ部が存在する(content-length > 0 である)場合には、直ちに別のパケットで送信されます。 At this point, the servlet container is presumably ready to start processing the request. As it does so, it can send the following messages back to the web server:
この時点では、おそらくServletコンテナはリクエスト処理を開始する用意ができています。 その場合には、以下のメッセージをWebサーバに返信することができます。
Each message is accompanied by a differently formatted packet of data. See Response Packet Structures below for details. 各メッセージは、異なるフォーマットのデータパケットで実現します。 詳しくは、以下で述べるレスポンスパケット構造を参照してください。
There is a bit of an XDR heritage to this protocol, but it differs in lots of ways (no 4 byte alignment, for example). このプロトコルは、XDRの特徴を少し受け継いでいますが、多くの点で異なっています(たとえば、4バイトのアライメントがないことです)。 Byte order: I am not clear about the endian-ness of the individual bytes. I'm guessing the bytes are little-endian, because that's what XDR specifies, and I'm guessing that sys/socket library is magically making that so (on the C side). If anyone with a better knowledge of socket calls can step in, that would be great. バイトオーダー: 私はバイトのエンディアンがどうなっているかはよくわかりません。 リトルエンディアンだと思うのですが、それはXDRがそうだからです。 そしておそらく(C言語側のコードで)sys/socketライブラリが自動的に(バイトオーダーに関して)処理してくれているのではないかと思います。 ソケット呼び出しについて誰かもっと詳しく知っている人が参加してくれればすばらしいと思います。 There are four data types in the protocol: bytes, booleans, integers and strings.
このプロトコルには、byte, boolean, integer, stringという4つのデータ型があります。
According to much of the code, the max packet size is 8 * 1024 bytes (8K). The actual length of the packet is encoded in the header. 多くのコードによると、最大パケットサイズは8 * 1024 バイト (8K) です。 パケットの実際の長さは、ヘッダ部にエンコードされます。
Packets sent from the server to the container begin with 0x1234. Packets sent from the container to the server begin with AB (that's the ASCII code for A followed by the ASCII code for B). After those first two bytes, there is an integer (encoded as above) with the length of the payload. Although this might suggest that the maximum payload could be as large as 2^16, in fact, the code sets the maximum to be 8K.
サーバからコンテナに送信されるパケットは、0x1234で始まります。 コンテナからサーバに送信されるパケットは、ABで始まります (つまり、ASCIIコードのAの直後にASCIIコードのBがきます)。 この最初の2バイトの直後に、送信データの長さの整数がきます(この前に説明したようにエンコードされています)。 つまり、論理的な最大データサイズは2^16ですが、実際の最大値は8Kに制限されています。
For most packets, the first byte of the payload encodes the type of message. The exception is for request body packets sent from the server to the container -- they are sent with a standard packet header (0x1234 and then length of the packet), but without any prefix code after that (this seems like a mistake to me). 大部分のパケットでは、送信データの最初の1バイトにメッセージのタイプがエンコードされています。 この例外は、サーバからコンテナに送信されるリクエスト内容のパケットです - これらは標準パケットヘッダ (0x1234の後にパケット長)を付加して送信されますが、その直後にプレフィクスコードはありません(これは私は間違いのように思います)。 The web server can send the following messages to the servlet container:
Webサーバは、以下のメッセージをServletコンテナに送信することができます。
The servlet container can send the following types of messages to the web server:
Servletコンテナは、Webサーバに以下のタイプのメッセージを送信することができます。
Each of the above messages has a different internal structure, detailed below. 上記のメッセージは、それぞれ別の内部構造を持っていますので、この後に説明します。
For messages from the server to the container of type "Forward Request": サーバからコンテナに送信される、タイプ"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
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
The request_headers have the following structure: 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)
req_header_name :=
sc_req_header_name | (string) [この解析方法については、この後を参照してください]
sc_req_header_name := 0xA0xx (integer)
req_header_value := (string)
The attributes are optional and have the following structure: attributesはオプションで、以下のような構造です。
attribute_name := (string) attribute_value := (string) Not that the all-important header is "content-length", because it determines whether or not the container looks for another packet immediately. "content-length"は極めて重要なヘッダであることに注意する必要があります。なぜなら、このヘッダはコンテナが次のパケットをすぐに探すかどうかを決めるからです。 Detailed description of the elements of Forward Request. 転送するリクエストの要素の詳細を以下に示します。
For all requests, this will be 2. See above for details on other prefix codes . すべてのリクエストで、この値は2になります。 詳しくは上記の プレフィックスコード を参照してください。
The HTTP method, encoded as a single byte: HTTPメソッドを、以下のように1バイトにエンコードしています。
These are all fairly self-explanatory. Each of these is required, and will be sent for every request. これらについては、名前を見れば簡単にわかるでしょう。 これらはすべて必須であり、リクエストごとに送信されます。
The structure of request_headers is the following: First, the number of headers num_headers is encoded. Then, a series of header name req_header_name / value req_header_value pairs follows. Common header names are encoded as integers, to save space. If the header name is not in the list of basic headers, it is encoded normally (as a string, with prefixed length). The list of common headers sc_req_header_nameand their codes is as follows (all are case-sensitive): request_headersの構造は以下の通りです。 最初に、ヘッダの数(num_headers)がエンコードされます。 次に、ヘッダ名(req_header_name)と値(req_header_value)の組の集合が続きます。 一般的なヘッダ名は、容量を節約するために整数としてエンコードします。 ヘッダ名が基本ヘッダ(=一般的なヘッダ)のリストにない場合には、普通に(最初に長さがついた文字列として)エンコードされます。 一般的なヘッダ(sc_req_header_name)のリストとそのコードは、以下に示します(大文字・小文字を区別します)。
The Java code that reads this grabs the first two-byte integer and if it sees an '0xA0' in the most significant byte, it uses the integer in the second byte as an index into an array of header names. If the first byte is not '0xA0', it assumes that the two-byte integer is the length of a string, which is then read in. これを読み込むJava言語のコードでは、まず最初の2バイトの整数を読み込んで、 MSB (Most Significant Byte)が'0xA0'の場合には、 2番目のバイトをヘッダ名の配列に対するインデックスである整数と見なします。 最初の1バイトが'0xA0'でない場合には、2バイトの整数が文字列の長さを表していると見なして、 それを読み込みます。 This works on the assumption that no header names will have length greater than 0x9999 (==0xA000 - 1), which is perfectly reasonable, though somewhat arbitrary. (If you, like me, started to think about the cookie spec here, and about how long headers can get, fear not -- this limit is on header names not header values . It seems unlikely that unmanageably huge header names will be showing up in the HTTP spec any time soon). これは、0x9999 (==0xA000 - 1)より長いヘッダ名が存在しないことを仮定すれば動作しますが、これはやや独断的ですが、きわめて妥当でしょう。(もし、あなたが私のようにcookieの仕様と、どのくらいの長さのヘッダを得ることができるかについて考え始めたとしても、恐れることはありません - というのは、これはヘッダの 名前 の制限であって、ヘッダの 値 の制限ではないからです。 管理できないくらい巨大なヘッダ名についてHTTP仕様が定義されることは、とてもありえません。) (訳注: 0x9999 は、0x9FFF の間違いだと思われる。) Note: The content-length header is extremely important. If it is present and non-zero, the container assumes that the request has a body (a POST request, for example), and immediately reads a separate packet off the input stream to get that body. 注意: content-lengthヘッダは極めて重要です。 このヘッダが存在して、0以外の値をとる場合には、コンテナはリクエストに(たとえば、POSTリクエストのように)ボディ部があるものとして、ただちにボディ部を取得するために別のパケットをインプットストリームから読み込みます。
The list of attributes prefixed with a ? (e.g. ?context) are all optional. For each, there is a single byte code to indicate the type of attribute, and then a string to give its value. They can be sent in any order (thogh the C code always sends them in the order listed below). A special terminating code is sent to signal the end of the list of optional attributes. The list of byte codes is: ?が先頭についた属性(例 ?context)のリストのすべては必須ではありません。 それぞれについて、属性のタイプを示す1バイトコードが定義されていて、文字列をこの値に変換します。 これらのヘッダは、(C言語のコードは常に以下の順序で送信するのですが)任意の順序で送信できます。 必須ではない属性のリストの最後を知らせるために、特別な終了コードを送信します。 バイトコードのリストは以下の通りです。
The context and servlet_path are not currently set by the C code, and most of the Java code completely ignores whatever is sent over for those fields (and some of it will actually break if a string is sent along after one of those codes). I don't know if this is a bug or an unimplemented feature or just vestigial code, but it's missing from both sides of the connection. contextとservlet_pathは、現在はC言語のコードでは設定されませんし、 Java言語のコードの大部分では、これらのフィールドが送信されても完全に無視されます (そして、これらのコードの後に文字列が送信された場合には、実際に失敗します)。 私はこれがバグなのか、未実装の仕様なのか、それとも単に昔のコードの痕跡なのかは知りませんが、 コネクションの両側で実装されていません。 The remote_user and auth_type presumably refer to HTTP-level authentication, and communicate the remote user's username and the type of authentication used to establish their identity (e.g. Basic, Digest). I'm not clear on why the password isn't also sent, but I don't know HTTP authentication inside and out. remote_userとauth_typeは、どうやらHTTPレベルの認証に対応しているらしく、 リモートのユーザのユーザ名とその本人確認をおこなうために使用した認証のタイプ(例:BASIC認証、ダイジェスト認証)のようです。 なぜパスワードが一緒に送信されないのかは、よくわかりませんが、私はHTTP認証についてはまったくわからないのです。 The query_string, ssl_cert, ssl_cipher, and ssl_session refer to the corresponding pieces of HTTP and HTTPS. query_stringとssl_cert、 ssl_cipher、ssl_sessionは、HTTPとHTTPSに相当する部分に関するものです。 The jvm_route, as I understand it, is used to support sticky sessions -- associating a user's sesson with a particular Tomcat instance in the presence of multiple, load-balancing servers. I don't know the details. jvm_routeは、私が理解している限りでは、スティッキ・セッションをサポートするために - つまり複数のサーバで負荷分散している時に、ユーザのセッションとある特定のTomcatインスタンスを関連付けるために使用しているようです。 私は詳しいことについては知りません。 Beyond this list of basic attributes, any number of other attributes can be sent via the req_attribute code (0x0A). A pair of strings to represent the attribute name and value are sent immediately after each instance of that code. Environment values are passed in via this method. この基本属性のリスト以外にも、他の多くの属性をreq_attributeコード (0x0A)を用いて送信します。 属性名と値を表す文字列のペアは、このコードのすぐ後に送信されます。 環境変数は、このメソッドを用いて渡します。 Finally, after all the attributes have been sent, the attribute terminator, 0xFF, is sent. This signals both the end of the list of attributes, and also then end of the Request Packets as a whole. 最後に、すべての属性を送信した後に、属性のターミネータとして、0xFFを送信します。 これは、属性のリストの終了とともに、リクエストパケット全体の終了を知らせます。 The server can also send a shutdown packet. To ensure some basic security, the container will only actually do the shutdown if the request comes from the same machine on which it's hosted. サーバは、shutdownパケットも送信することができます。 基本的なセキュリティを保証するために、実際にはコンテナはリクエストが運用しているマシンと同じマシンから送信されてくる場合にのみ、終了します。
For messages which the container can send back to the server.
AJP13_SEND_BODY_CHUNK :=
prefix_code 3
chunk_length (integer)
chunk *(byte)
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)
コンテナがサーバに返信することができるメッセージは以下の通りです。
AJP13_SEND_BODY_CHUNK :=
prefix_code 3
chunk_length (integer)
chunk *(byte)
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) [この解析方法については、この後を参照してください]
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)
Details: 詳細は以下の通りです。
The chunk is basically binary data, and is sent directly back to the browser. チャンクは基本的にバイナリデータで、ブラウザに直接返送されます。
The status code and message are the usual HTTP things (e.g. "200" and "OK"). The response header names are encoded the same way the request header names are. See above for details about how the the codes are distinguished from the strings. The codes for common headers are: ステータスコードとメッセージは、通常のHTTPの定義に従います (例, "200"と"OK")。 レスポンスヘッダ名は、リクエストヘッダ名と同じ方法でエンコードします。 コードと文字列を区別する方法についての詳しい説明は、 上記 を参照してください。 一般的なヘッダのコードは、以下の通りです。
After the code or the string header name, the header value is immediately encoded. コードや文字列のヘッダ名のすぐ後に、ヘッダの値がエンコードされます。
Signals the end of this request-handling cycle. If the reuse flag is true (==1), this TCP connection can now be used to handle new incoming requests. If reuse is false (anything other than 1 in the actual C code), the connection should be closed. このリクエスト処理サイクルの終了を知らせます。 reuseフラグがtrue (==1)の場合には、このTCPコネクションを新しく到着するリクエストを処理するために使用できます。 reuseがfalse (実際のC言語のコードでは1以外の値です)の場合には、 このコネクションをクローズしなければいけません。
The container asks for more data from the request (if the body was
too large to fit in the first packet sent over). The server will send a
body packet back with an amount of data which is the minimum of the
request_length, the maximum send body size (XXX), and the
number of bytes actually left to send from the request body.
(リクエストのボディ部が非常に大きくて、送信された最初のパケット内に収まらなかった場合に)コンテナがリクエストからの追加データを要求します。
サーバは、ボディ部のパケットを、ある程度のデータと一緒に返信します。その中には少なくとも、request_lengthと、最大送信ボディサイズ (XXX)、それにリクエストボディからまだ送信されていないバイト数が含まれています。
What happens if the request headers > max packet size? There is no provision to send a second packet of request headers in case there are more than 8K (I think this is correctly handled for response headers, though I'm not certain). I don't know if there is a way to get more than 8K worth of data into that initial set of request headers, but I'll bet there is (combine long cookies with long ssl information and a lot of environment variables, and you should hit 8K easily). I think the connector would just fail before trying to send any headers in this case, but I'm not certain. リクエストヘッダの合計サイズが最大パケットサイズを越えた時に、何が起こるでしょうか? 8K以上の場合には、リクエストヘッダの二番目のパケットを送信する準備がされていません(確かめていませんが、私はレスポンスヘッダについてはうまく処理できると思います)。 私は、リクエストヘッダの初期集合に入っている8K以上のデータを取得する方法が存在するかどうかは知りませんが、おそらく存在するでしょう(長いSSL情報を持った長いCookieと多量の環境変数を組み合わせれば、簡単に8Kを越えるでしょう)。 私は、このような場合にヘッダが送信できるかを試す前に、コネクタが落ちるのではないかと思いますが、確かめたわけではありません。 What about authentication? There doesn't seem to be any authentication of the connection between the web server and the container. This strikes me as potentially dangerous. 認証についてはどうなのでしょうか? Webサーバとコンテナの間でコネクションの認証が行われてないように思われます。 これについては、私は潜在的な危険を感じてます。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
[訳注: これは鰈崎 義之が翻訳しました。日本語訳に対するコメントがあれば、こちらに送って下さい。(風間氏翻訳のAJPv13.htmlをベースに一部追加・修正)]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||