HTTP 模型
Akka HTTP model contains a deeply structured, fully immutable, case-class based model of all the major HTTP data structures, like HTTP requests, responses and common headers. It lives in the akka-http-core module and forms the basis for most of Akka HTTP’s APIs.
Akka HTTP 模型包含一套,结构严密,全不可变,基于 case-class 模型对应主要的 HTTP 数据结构,例如 HTTP 请求、响应和通用的(HTTP)头。 这套模型在 akka-http-core 模块里,并构成了大部分 Akka HTTP API 基础。
Overview
概述
Since akka-http-core provides the central HTTP data structures you will find the following import in quite a few places around the code base (and probably your own code as well):
既然 akka-http-core 提供了主要的 HTTP 数据类型,你会发现以下的引入会经常出现在相当多的代码里(或许还有你自己的代码):
- Scala
-
import akka.http.scaladsl.model._
- Java
-
import akka.http.javadsl.model.*; import akka.http.javadsl.model.headers.*; import java.util.Optional;
This brings all of the most relevant types in scope, mainly:
这个包基本上引入了包括全部的主要相关类型,比如:
HttpRequest
HttpRequest
andHttpResponse
HttpResponse
, the central message modelheaders
, the package containing all the predefined HTTP header models and supporting types- Supporting types like
Uri
Uri
,HttpMethods
HttpMethods
,MediaTypes
MediaTypes
,StatusCodes
StatusCodes
, etc. -
HttpRequest
HttpRequest
和HttpResponse
HttpResponse
,主要的消息模型; headers
, 这个包包含了所有预定义的 HTTP 头模型和支持的辅助类型;- 支持的辅助类型如:
Uri
Uri
、HttpMethods
HttpMethods
、MediaTypes
MediaTypes
、StatusCodes
StatusCodes
等等。
A common pattern is that the model of a certain entity is represented by an immutable type (class or trait), while the actual instances of the entity defined by the HTTP spec live in an accompanying object carrying the name of the type plus a trailing plural ‘s’.
在 Akka HTTP 的包定义规则里,一个比较通用的做法是,一个数据类型的抽象一般都是用一个不可变的类型(可以是 class 或者 trait)进行描述,而对应 HTTP 规范中具体的实例/值则由其对应的伴生对象生成并存放。伴生对象的命名规则为相关定义的名称复数,也就是在其对应规范类型名称后加‘s’
For example:
例如:
- Defined
HttpMethod
HttpMethod
instances live inare defined as static fields of theHttpMethods
HttpMethods
objectclass. - Defined
HttpCharset
HttpCharset
instances live inare defined as static fields of theHttpCharsets
HttpCharsets
objectclass. - Defined
HttpEncoding
HttpEncoding
instances live inare defined as static fields of theHttpEncodings
HttpEncodings
objectclass. - Defined
HttpProtocol
HttpProtocol
instances live inare defined as static fields of theHttpProtocols
HttpProtocols
objectclass. - Defined
MediaType
MediaType
instances live inare defined as static fields of theMediaTypes
MediaTypes
objectclass. - Defined
StatusCode
StatusCode
instances live inare defined as static fields of theStatusCodes
StatusCodes
objectclass. -
定义
HttpMethod
HttpMethod
实例 置于成一个静态字段置于HttpMethods
HttpMethods
objectclass. - 定义
HttpCharset
HttpCharset
实例 置于成一个静态字段置于HttpCharsets
HttpCharsets
objectclass. - 定义
HttpEncoding
HttpEncoding
实例 置于成一个静态字段置于HttpEncodings
HttpEncodings
objectclass. - 定义
HttpProtocol
HttpProtocol
实例 置于成一个静态字段置于HttpProtocols
HttpProtocols
objectclass. - 定义
MediaType
MediaType
实例 置于成一个静态字段置于MediaTypes
MediaTypes
objectclass. - 定义
StatusCode
StatusCode
实例 置于成一个静态字段置于StatusCodes
StatusCodes
objectclass.
HttpRequest
HttpRequest
HttpRequest
and HttpResponse
HttpResponse
are the basic caseimmutable classes representing HTTP messages.
HttpRequest
HttpRequest
和 HttpResponse
HttpResponse
是表示 HTTP 消息的基本 caseimmutable 类。
An HttpRequest
HttpRequest
consists of
一个 HttpRequest
HttpRequest
包括
- a method (GET, POST, etc.)
- a URI (see URI model for more information)
- a seq of headers
- an entity (body data)
- a protocol
-
请求方法 (GET, POST, 等等)
- URI 地址(有关更多信息见 URI 模型)
- (HTTP)头列表
- 实体(正文数据)
- 协议
Here are some examples how to construct an HttpRequest
HttpRequest
:
这是如何构造一个 HttpRequest
HttpRequest
的一些示例:
- Scala
-
import HttpMethods._ // construct a simple GET request to `homeUri` val homeUri = Uri("/abc") HttpRequest(GET, uri = homeUri) // construct simple GET request to "/index" (implicit string to Uri conversion) HttpRequest(GET, uri = "/index") // construct simple POST request containing entity val data = ByteString("abc") HttpRequest(POST, uri = "/receive", entity = data) // customize every detail of HTTP request import HttpProtocols._ import MediaTypes._ import HttpCharsets._ val userData = ByteString("abc") val authorization = headers.Authorization(BasicHttpCredentials("user", "pass")) HttpRequest( PUT, uri = "/user", entity = HttpEntity(`text/plain` withCharset `UTF-8`, userData), headers = List(authorization), protocol = `HTTP/1.0`)
- Java
-
// construct a simple GET request to `homeUri` Uri homeUri = Uri.create("/home"); HttpRequest request1 = HttpRequest.create().withUri(homeUri); // construct simple GET request to "/index" using helper methods HttpRequest request2 = HttpRequest.GET("/index"); // construct simple POST request containing entity ByteString data = ByteString.fromString("abc"); HttpRequest postRequest1 = HttpRequest.POST("/receive").withEntity(data); // customize every detail of HTTP request //import HttpProtocols.* //import MediaTypes.* Authorization authorization = Authorization.basic("user", "pass"); HttpRequest complexRequest = HttpRequest.PUT("/user") .withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, "abc")) .addHeader(authorization) .withProtocol(HttpProtocols.HTTP_1_0);
All parameters of HttpRequest.apply
have default values set, so headers
for example don’t need to be specified if there are none. Many of the parameters types (like HttpEntity
HttpEntity
and Uri
Uri
) define implicit conversions for common use cases to simplify the creation of request and response instances.
HttpRequest.apply
的所有参数都有设置默认值,因此如果 HTTP 请求头,则不需要指定 headers
参数。 许多参数类型(如: HttpEntity
HttpEntity
和 Uri
Uri
)为常见用例定义了隐式转换,以简化请求和响应实例的生成。
In its basic form HttpRequest.create
creates an empty default GET request without headers which can then be transformed using one of the withX
methods, addHeader
, or addHeaders
. Each of those will create a new immutable instance, so instances can be shared freely. There exist some overloads for HttpRequest.create
that simplify creating requests for common cases. Also, to aid readability, there are predefined alternatives for create
named after HTTP methods to create a request with a given method and URI directly.
HttpRequest.create
以基本形式创建一个没有请求头的空的默认 GET 请求,然后可以通过使用 withX
的其中一个方法对其修改。如 addHeader
或 addHeaders
。 因为每个 withX
方法都将创建一个新的不可变实例,所以实例可以自由共享(译注:多个请求头实例之间可以共享相同的 header、uri、protocol等)。 HttpRequest.create
存在一些重载方法来简化常见情况下请求的创建。另外,为了提供可读性,预定义了以 create
名字后接 HTTP 方法名的替代方法,用以直接使用结合实际的方法和 URI 创建请求。
Synthetic Headers
合成 HTTP 头
In some cases it may be necessary to deviate from fully RFC-Compliant behavior. For instance, Amazon S3 treats the +
character in the path part of the URL as a space, even though the RFC specifies that this behavior should be limited exclusively to the query portion of the URI.
某些情况下可能有必要偏离完全符合 RFC 的行为。例如,Amazon S3 将 URL 路径部分中的 +
字符当作空格,即使 RFC 规定此行为仅限于 URI 的查询部分。
In order to work around these types of edge cases, Akka HTTP provides for the ability to provide extra, non-standard information to the request via synthetic headers. These headers are not passed to the client but are instead consumed by the request engine and used to override default behavior.
为解决这些边缘情况,Akka HTTP 提供了通过合成头向请求提供非标准信息的能力。这些头不会传递给客户端,但由请求引擎消息,并且用于覆盖默认设置。
For instance, in order to provide a raw request uri, bypassing the default url normalization, you could do the following:
例如:为了提供一个原始请求 uri,绕过默认的 url 规范化,你可按下面方式做:
- Scala
-
import akka.http.scaladsl.model.headers.`Raw-Request-URI` val req = HttpRequest(uri = "/ignored", headers = List(`Raw-Request-URI`("/a/b%2Bc")))
- Java
-
// imports akka.http.javadsl.model.headers.RawRequestURI HttpRequest.create("/ignored").addHeader(RawRequestURI.create("/a/b%2Bc"));
HttpResponse
An HttpResponse
HttpResponse
consists of
一个 HttpResponse
HttpResponse
包括
- a status code
- a
Seq
list of headers - an entity (body data)
- a protocol
-
状态码
- (HTTP)头
Seq
list - 实体 (正文数据)
- 协议
Here are some examples how to construct an HttpResponse
HttpResponse
:
这是如何构造一个 HttpResponse
HttpResponse
的一些示例:
- Scala
-
import StatusCodes._ // simple OK response without data created using the integer status code HttpResponse(200) // 404 response created using the named StatusCode constant HttpResponse(NotFound) // 404 response with a body explaining the error HttpResponse(404, entity = "Unfortunately, the resource couldn't be found.") // A redirecting response containing an extra header val locationHeader = headers.Location("http://example.com/other") HttpResponse(Found, headers = List(locationHeader))
- Java
-
// simple OK response without data created using the integer status code HttpResponse ok = HttpResponse.create().withStatus(200); // 404 response created using the named StatusCode constant HttpResponse notFound = HttpResponse.create().withStatus(StatusCodes.NOT_FOUND); // 404 response with a body explaining the error HttpResponse notFoundCustom = HttpResponse.create() .withStatus(404) .withEntity("Unfortunately, the resource couldn't be found."); // A redirecting response containing an extra header Location locationHeader = Location.create("http://example.com/other"); HttpResponse redirectResponse = HttpResponse.create() .withStatus(StatusCodes.FOUND) .addHeader(locationHeader);
In addition to the simple HttpEntity
HttpEntity
constructorsHttpEntities.create
methods which create an entity from a fixed String
or ByteString
ByteString
as shown here the Akka HTTP model defines a number of subclasses of HttpEntity
HttpEntity
which allow body data to be specified as a stream of bytes. All of these types can be created using the method on HttpEntites
.
除了从固定的 String
或 ByteString
ByteString
创建实体的简单 HttpEntity
HttpEntity
构造器HttpEntities.create
方法, Akka HTTP 模型还定义了大量 HttpEntity
HttpEntity
的子类型处理字节学流式的正文数据。@java[所以这些类型都可以通过 HttpEntites
上的方法创建。]
HttpEntity
An HttpEntity
HttpEntity
carries the data bytes of a message together with its Content-Type and, if known, its Content-Length. In Akka HTTP there are five different kinds of entities which model the various ways that message content can be received or sent:
一个 HttpEntity
HttpEntity
携带消息的数据字节以及它的 Content-Type 和 Content-Length(如果知道)。 在 Akka HTTP 里存在五种不同的实体,可以接收或发送各种方式的消息内容。
- HttpEntity.StrictHttpEntityStrict
- The simplest entity, which is used when all the entity are already available in memory. It wraps a plain
ByteString
ByteString
and represents a standard, unchunked entity with a knownContent-Length
. -
最简单的实体,用于当整个实体在内存里可用时。它把一个
ByteString
ByteString
包装成一个标准的,非分块的,以及带有已知Content-Length
(内容长度)的实体。 - HttpEntity.DefaultHttpEntityDefault
- The general, unchunked HTTP/1.1 message entity. It has a known length and presents its data as a
Source<ByteString, ?>
Source[ByteString, _]
which can be only materialized once. It is an error if the provided source doesn’t produce exactly as many bytes as specified. The distinction ofStrict
HttpEntityStrict
andDefault
HttpEntityDefault
is an API-only one. On the wire, both kinds of entities look the same. -
通用的,非分块的 HTTP/1.1 消息实体。它具有已知长度并其内容数据为一个
Source<ByteString, ?>
Source[ByteString, _]
,该实体只能被实例化一次。 如果提供的数据来源不能生成与指定长度完全相同的字节,则它是一个错误。@scala[Strict
]HttpEntityStrict
和Default
HttpEntityDefault
的区别只在 API 上。 在实际传输上,两者看起来一样的。 - HttpEntity.ChunkedHttpEntityChunked
- The model for HTTP/1.1 chunked content (i.e. sent with
Transfer-Encoding: chunked
). The content length is unknown and the individual chunks are presented as aSource[HttpEntity.ChunkStreamPart]
Source<ChunkStreamPart, ?>
Source[ChunkStreamPart, ?]
. AChunkStreamPart
is either a non-emptyChunk
chunk or aLastChunk
the empty last chunk containing optional trailer headers. The stream consists of zero or moreChunked
non-empty chunks parts and can be terminated by an optionalLastChunk
partlast chunk. -
模型为 HTTP/1.1 分块内容(既,发送时使用
Transfer-Encoding: chunked
HTTP 头)。 内容长度是未知的且单独的块被表示为Source[HttpEntity.ChunkStreamPart]
Source<ChunkStreamPart, ?>
Source[ChunkStreamPart, ?]
。 一个ChunkStreamPart
是非空的Chunk
chunk 或 包含可选头的 aLastChunk
空的最后一块 。 流包括零个或多个Chunked
非空块 部分,且能被一个可选的LastChunk
部分最后块 终止。 - HttpEntity.CloseDelimitedHttpEntityCloseDelimited
- An unchunked entity of unknown length that is implicitly delimited by closing the connection (
Connection: close
). The content data are presented as aSource<ByteString, ?>
Source[ByteString, _]
. Since the connection must be closed after sending an entity of this type it can only be used on the server-side for sending a response. Also, the main purpose ofCloseDelimited
entities is compatibility with HTTP/1.0 peers, which do not support chunked transfer encoding. If you are building a new application and are not constrained by legacy requirements you shouldn’t rely onCloseDelimited
entities, since implicit terminate-by-connection-close is not a robust way of signaling response end, especially in the presence of proxies. Additionally this type of entity prevents connection reuse which can seriously degrade performance. UseHttpEntity.Chunked
HttpEntityChunked
instead! -
未知长度的非块实体,它的长度由连接闭关隐式界定(
Connection: close
)。内容数据被表示为Source<ByteString, ?>
Source[ByteString, _]
。 因此,这个类型的实体发送后连接必需被关闭,它只能用于服务端发送响应。 同时,设计CloseDelimited
实体的主要原因是兼容 HTTP/1.0,因为旧的协议不支持分块传输编码。如果你正在构建新的应用程序,且没有受遗留需求的约束, 不应该使用CloseDelimited
,因为隐式的连接关闭时终止(terminate-by-connection-close)是不可靠的信令响应结束方式,特别是在代理的情况下(现今的服务大量放置在代理服务器后)。 再加上这个类型的实体阻止连接复用,会严重影响性能。使用HttpEntity.Chunked
HttpEntityChunked
替代它! - HttpEntity.IndefiniteLengthHttpEntityIndefiniteLength
- A streaming entity of unspecified length for use in a
Multipart.BodyPart
. -
在
Multipart.BodyPart
中使用的未指定长度的流式实体。
Entity types Strict
HttpEntityStrict
, Default
HttpEntityDefault
, and Chunked
HttpEntityChunked
are a subtype of HttpEntity.Regular
RequestEntity
RequestEntity
which allows to use them for requests and responses. In contrast, HttpEntity.CloseDelimited
HttpEntityCloseDelimited
can only be used for responses.
实体类型 Strict
HttpEntityStrict
、@scala[Default
]HttpEntityDefault
和 Chunked
HttpEntityChunked
是 HttpEntity.Regular
RequestEntity
RequestEntity
的子类型。 它们适用于请求和响应。相反,@scala[HttpEntity.CloseDelimited
]HttpEntityCloseDelimited
只能被用于响应。
Streaming entity types (i.e. all but Strict
HttpEntityStrict
) cannot be shared or serialized. To create a strict, shareable copy of an entity or message use HttpEntity.toStrict
or HttpMessage.toStrict
which returns a Future
CompletionStage
of the object with the body data collected into a ByteString
ByteString
.
流式实体类型(@scala[Strict
]HttpEntityStrict
除外)不能被共享或序列化。要创建一个严格的(strict)、可共享的实体或消息复本,可使用 HttpEntity.toStrict
或 HttpMessage.toStrict
返回一个 Future
CompletionStage
,正文数据被收集到一个 ByteString
ByteString
里面。
- 为什么需要
toStrict
? TODO toStrict
为什么需要使用Future
包裹? TODO
The HttpEntity
HttpEntity
companion objectclass HttpEntities
contains several helper constructorsstatic methods to create entities from common types easily.
在 HttpEntity
HttpEntity
伴生对像HttpEntities
类 包含 一些辅助构造函数静态方法 使从常用类型创建实体更容易。
You can pattern match overuse the subtypesisX
methods of HttpEntity
HttpEntity
to find out of which subclass an entity is if you want to provide special handling for each of the subtypes. However, in many cases a recipient of an HttpEntity
HttpEntity
doesn’t care about of which subtype an entity is (and how data is transported exactly on the HTTP layer). Therefore, the general method HttpEntity.dataBytes
HttpEntity.getDataBytes()
is provided which returns a Source<ByteString, ?>
Source[ByteString, _]
that allows access to the data of an entity regardless of its concrete subtype.
如果要对每个子类型进行特殊处理,可以 在使用 HttpEntity
HttpEntity
的子类型上进行模式匹配上的 isX
方法找出实体属于哪个子类型。 然而,在大部分情况下一个 HttpEntity
HttpEntity
的接收方并不关心实体的子类型(以及数据在 HTTP 层是如何传输的)。那么,通用方法 HttpEntity.dataBytes
HttpEntity.getDataBytes()
返回一个 Source<ByteString, ?>
Source[ByteString, _]
,在不管它的具体子类型的情况下允许访问实体的数据。
什么时候使用哪种子类型?
- Use
Strict
HttpEntityStrict
if the amount of data is “small” and already available in memory (e.g. as aString
orByteString
ByteString
) - Use
Default
HttpEntityDefault
if the data is generated by a streaming data source and the size of the data is known - Use
Chunked
HttpEntityChunked
for an entity of unknown length - Use
CloseDelimited
HttpEntityCloseDelimited
for a response as a legacy alternative toChunked
HttpEntityChunked
if the client doesn’t support chunked transfer encoding. Otherwise useChunked
HttpEntityChunked
! - In a
Multipart.BodyPart
useIndefiniteLength
HttpEntityIndefiniteLength
for content of unknown length. -
Strict
HttpEntityStrict
,如果数据里“小”并且已经在内存中可用(例如:String
orByteString
ByteString
); Default
HttpEntityDefault
,如果数据由流数据源产生且数据的大小已知;Chunked
HttpEntityChunked
,数据长度未知的实体;- 如果客户端不支持分块传输编码,使用
CloseDelimited
HttpEntityCloseDelimited
作为响应替代Chunked
HttpEntityChunked
。否则使用Chunked
HttpEntityChunked
! - 在
Multipart.BodyPart
中,@scala[IndefiniteLength
]HttpEntityIndefiniteLength
用于未知长度的内容。
警告
When you receive a non-strict message from a connection then additional data are only read from the network when you request them by consuming the entity data stream. This means that, if you don’t consume the entity stream then the connection will effectively be stalled. In particular no subsequent message (request or response) will be read from the connection as the entity of the current message “blocks” the stream. Therefore you must make sure that you always consume the entity data, even in the case that you are not actually interested in it!
当你从连接上收到一个非严格的消息时,只有当消费实体数据流时,才会继续中网络上读取额外的数据。这意味着,如果你 停止 消费实体流,则连接将停摆。 连接上后续的消息(请求或响应)将不会被读取,因为当前消息的实体“阻塞”了这个流。为此,你必需确认你总是消费了实体数据,即使你对它没有兴趣! (译注:使用 entity.discardBytes
忽略实体数据)
Limiting message entity length
限制消息实体长度
All message entities that Akka HTTP reads from the network automatically get a length verification check attached to them. This check makes sure that the total entity size is less than or equal to the configured max-content-length
[1], which is an important defense against certain Denial-of-Service attacks. However, a single global limit for all requests (or responses) is often too inflexible for applications that need to allow large limits for some requests (or responses) but want to clamp down on all messages not belonging into that group.
Akka HTTP 从网络上读取的所有消息实体都会自动获得附加的一个长度检查。该检查确认实体总大小小于等于配置的 max-content-length
[1], 以便作为对抗某些拒绝服务攻击的重要防范手段。然而,对于需要允许 某些 请求(或响应)有较大的限制,而要限制不属于该组的所有消息的应用程序时, 对所有请求(或响应)的全局限制不够灵活。
In order to give you maximum flexibility in defining entity size limits according to your needs the HttpEntity
HttpEntity
features a withSizeLimit
method, which lets you adjust the globally configured maximum size for this particular entity, be it to increase or decrease any previously set value. This means that your application will receive all requests (or responses) from the HTTP layer, even the ones whose Content-Length
exceeds the configured limit (because you might want to increase the limit yourself). Only when the actual data stream Source
Source
contained in the entity is materialized will the boundary checks be actually applied. In case the length verification fails the respective stream will be terminated with an EntityStreamSizeException
either directly at materialization time (if the Content-Length
is known) or whenever more data bytes than allowed have been read.
为了给予你根据你的需要定义实体大小限制条件的最大灵活性, HttpEntity
HttpEntity
提供了 withSizeLimit
方法, 该方法让你调整此特定实体的全局配置最大大小,无论该大小是增加还是减少任何先前设置的值。 这意味着你的应用程序将从 HTTP 层收到所有请求(或响应),甚至 Content-Length
超过配置的限制的请求(或响应)(因为你可能希望自己增加限制)。 只有当实际数据流 Source
Source
具现化后,边界检查才会被执行。如果长度校验失效,对应的数据流就抛出 EntityStreamSizeException
并终结, 终止可以直接发生在具现化的时候(Content-Length
已知)或者再读入多几个超标字节之后。
When called on Strict
entities the withSizeLimit
method will return the entity itself if the length is within the bound, otherwise a Default
entity with a single element data stream. This allows for potential refinement of the entity size limit at a later point (before materialization of the data stream).
当在 Strict
实体上调用 withSizeLimit
方法时,如果长度(设置的)在(配置的)范围内,将返回它自己,否则返回带单一元素的数据流的 Default
实体。 这允许晚一点的时候(数据流具现化之前)再去做实体大小的限制
By default all message entities produced by the HTTP layer automatically carry the limit that is defined in the application’s max-content-length
config setting. If the entity is transformed in a way that changes the content-length and then another limit is applied then this new limit will be evaluated against the new content-length. If the entity is transformed in a way that changes the content-length and no new limit is applied then the previous limit will be applied against the previous content-length. Generally this behavior should be in line with your expectations.
默认情况下,所有在 HTTP 层生成的消息实体自动带有由应用程序的 max-content-length
配置设置的长度限制。如果实体转换改变了内容长度并设置了新的限制,则这个新的限制将应用予新内容长度。 如果实体转换改变了内容长度并不设置新的限制,则之前的限制将适用于之前的内容长度。
[1] akka.http.parsing.max-content-length (applying to server- as well as client-side), akka.http.server.parsing.max-content-length (server-side only), akka.http.client.parsing.max-content-length (client-side only) or akka.http.host-connection-pool.client.parsing.max-content-length (only host-connection-pools)
[1] akka.http.parsing.max-content-length(应用于服务端,也应用于客户端)、 akka.http.server.parsing.max-content-length (server-side only)、 akka.http.client.parsing.max-content-length (client-side only) 或 akka.http.host-connection-pool.client.parsing.max-content-length (只用 host-connection-pools)
Special processing for HEAD requests
HEAD 请求的特殊处理
RFC 7230 defines very clear rules for the entity length of HTTP messages.
RFC 7230 为 HTTP 消息的实体长度定义了很明确的规则。
Especially this rule requires special treatment in Akka HTTP:
特别是此规则需要在 Akka HTTP 中进行特别对待:
Any response to a HEAD request and any response with a 1xx (Informational), 204 (No Content), or 304 (Not Modified) status code is always terminated by the first empty line after the header fields, regardless of the header fields present in the message, and thus cannot contain a message body.
对 HEAD 请求的任何响应以及任何响应为 1xx(Informational)、204(No Content)或 304(Not Modified)状态码的信息都是使用 header 字段后的第一个空行作为终止, 不论在消息中的 header 字段有何表示,因为不能包含消息正文(译注:就算指定的 Content-Length,也不能包含消息正文)。
Responses to HEAD requests introduce the complexity that Content-Length or Transfer-Encoding headers can be present but the entity is empty. This is modeled by allowing HttpEntity.DefaultHttpEntityDefault and HttpEntity.ChunkedHttpEntityChunked to be used for HEAD responses with an empty data stream.
针对 HEAD 请求的响应引入了可以存在 Content-Length 或 Transfer-Encoding 头,但实体内容为空的复杂性。这通过允许 HttpEntity.DefaultHttpEntityDefault 和 HttpEntity.ChunkedHttpEntityChunked 使用具有空的数据流的 HEAD 响应来建模。
Also, when a HEAD response has an HttpEntity.CloseDelimitedHttpEntityCloseDelimited entity the Akka HTTP implementation will not close the connection after the response has been sent. This allows the sending of HEAD responses without Content-Length header across persistent HTTP connections.
并且,当 HEAD 响应是一个 HttpEntity.CloseDelimitedHttpEntityCloseDelimited 实体时,Akka HTTP 实现将 不会 在发送响应后关闭连接。 这允许在持久化的 HTTP 连接上发送不带 Content-Length 头的 HEAD 响应。
Header Model
头域模型
Akka HTTP contains a rich model of the most common HTTP headers. Parsing and rendering is done automatically so that applications don’t need to care for the actual syntax of headers. Headers not modelled explicitly are represented as a RawHeader
RawHeader
, which is essentially a String/String name/value pair.
Akka HTTP 包含最常见 HTTP 头的丰富模型。解析和渲染是自动完成的,因此应用程序不需要关心头域的实际语法。 若头域未显示建模则表示为 RawHeader
RawHeader
,它本质上是一个 String/String name/value 形式的键值对。
See these examples of how to deal with headers:
查看这些示例如何处理头域:
- Scala
-
import akka.http.scaladsl.model.headers._ // create a ``Location`` header val loc = Location("http://example.com/other") // create an ``Authorization`` header with HTTP Basic authentication data val auth = Authorization(BasicHttpCredentials("joe", "josepp")) // custom type case class User(name: String, pass: String) // a method that extracts basic HTTP credentials from a request def credentialsOfRequest(req: HttpRequest): Option[User] = for { Authorization(BasicHttpCredentials(user, pass)) <- req.header[Authorization] } yield User(user, pass)
- Java
-
// create a ``Location`` header Location locationHeader = Location.create("http://example.com/other"); // create an ``Authorization`` header with HTTP Basic authentication data Authorization authorization = Authorization.basic("user", "pass"); // a method that extracts basic HTTP credentials from a request private Optional<BasicHttpCredentials> getCredentialsOfRequest(HttpRequest request) { Optional<Authorization> auth = request.getHeader(Authorization.class); if (auth.isPresent() && auth.get().credentials() instanceof BasicHttpCredentials) return Optional.of((BasicHttpCredentials) auth.get().credentials()); else return Optional.empty(); }
HTTP Headers
HTTP 头
When the Akka HTTP server receives an HTTP request it tries to parse all its headers into their respective model classes. Independently of whether this succeeds or not, the HTTP layer will always pass on all received headers to the application. Unknown headers as well as ones with invalid syntax (according to the header parser) will be made available as RawHeader
RawHeader
instances. For the ones exhibiting parsing errors a warning message is logged depending on the value of the illegal-header-warnings
config setting.
当 Akka HTTP 服务器收到一个 HTTP 请求,它将尝试解析所有头域到它们所表示的模型类。无论成功与否,HTTP 层始终将传递所有收到的头域给应用程序。 未知头域甚至一些使用了无效标记(基于头域语法分析)的信息都将做为 RawHeader
RawHeader
的实例。出现解析错误的头域会有相应的警告信息记录在日志中 ,是否记录取决于 illegal-header-warnings
这个配置的值。
Some headers have special status in HTTP and are therefore treated differently from “regular” headers:
某些头域在 HTTP 里有特殊的状态,因此它们与“普通”的头域的处理方式有所不同:
- Content-Type
- The Content-Type of an HTTP message is modeled as the
contentType
field of theHttpEntity
HttpEntity
. TheContent-Type
header therefore doesn’t appear in theheaders
sequence of a message. Also, aContent-Type
header instance that is explicitly added to theheaders
of a request or response will not be rendered onto the wire and trigger a warning being logged instead! -
HTTP 消息的 Content-Type 建模为
HttpEntity
HttpEntity
的contentType
字段。Content-Type
头因此不会出现在消息的headers
列表里。即使一个Content-Type
头实例被显示地添加到请求或响应的headers
字段里,它也不会被生成到最终的通讯线路上,而是生成一个警告信息并记录日志。 - Transfer-Encoding
- Messages with
Transfer-Encoding: chunked
are represented via theHttpEntity.Chunked
as aHttpEntityChunked
entity. As such chunked messages that do not have another deeper nested transfer encoding will not have aTransfer-Encoding
header in theirheaders
sequencelist. Similarly, aTransfer-Encoding
header instance that is explicitly added to theheaders
of a request or response will not be rendered onto the wire and trigger a warning being logged instead! -
带有
Transfer-Encoding: chunked
的消息由HttpEntity.Chunked
HttpEntityChunked
实体表示。 因此,分块信息里,如果没有更深层次嵌套传输编码的,将不会在其头域列headers
里带有 Transfer-Encoding 类型的头域。即使一个Transfer-Encoding
类型的实例被强制加到头域列headers
中,其内容也不会被生成到最终通讯渠道上,而是生成一个警告信息并记录在日志里。 - Content-Length
- The content length of a message is modelled via its HttpEntity. As such no
Content-Length
header will ever be part of a message’sheader
sequence. Similarly, aContent-Length
header instance that is explicitly added to theheaders
of a request or response will not be rendered onto the wire and trigger a warning being logged instead! -
一个消息的内容长度由它的 HttpEntity 定义。因此,
Content-Length
头域永远不会成为消息头域串的一部分。 同样的,一个Content-Length
头实例被强制添加到请求的或响应的headers
,也不会被生成到最终的通讯渠道上,而是生成一个警告信息并记录在日志里。 - Server
- A
Server
header is usually added automatically to any response and its value can be configured via theakka.http.server.server-header
setting. Additionally an application can override the configured header with a custom one by adding it to the response’sheader
sequence. -
一个
Server
头通常自动添加到响应,它的值可通过akka.http.server.server-header
设置。另外,应用程序可以通过增加一个自定义的新实例到响应信息的headers
串中以覆盖之前设置的值。 - User-Agent
- A
User-Agent
header is usually added automatically to any request and its value can be configured via theakka.http.client.user-agent-header
setting. Additionally an application can override the configured header with a custom one by adding it to the request’sheader
sequence. -
一个
User-Agent
头通常自动添加到请求,它的值可通过akka.http.client.user-agent-header
设置。另外,应用程序可以通过增加一个自定义的新实例到请求信息的headers
串中以覆盖之前设置的值。 - Date
- The
Date
Date
response header is added automatically but can be overridden by supplying it manually. - Connection
- On the server-side Akka HTTP watches for explicitly added
Connection: close
response headers and as such honors the potential wish of the application to close the connection after the respective response has been sent out. The actual logic for determining whether to close the connection is quite involved. It takes into account the request’s method, protocol and potentialConnection
Connection
header as well as the response’s protocol, entity and potentialConnection
Connection
header. See this test for a full table of what happens when. -
在服务器端,Akka HTTP 监视器强制添加
Connection: close
响应头,以便程序在送出响应后希望断开相关连接的愿望。 确定是否决定关闭连接的逻辑相当的复杂。要考虑到请求的方法、协议类型和相关的Connection
Connection
头以及响应的协议类型、实体和相关的Connection
Connection
头。有这些情况发生的完整列表,参见 这个测试 。 - Strict-Transport-Security
- HTTP Strict Transport Security (HSTS) is a web security policy mechanism which is communicated by the
Strict-Transport-Security
header. The most important security vulnerability that HSTS can fix is SSL-stripping man-in-the-middle attacks. The SSL-stripping attack works by transparently converting a secure HTTPS connection into a plain HTTP connection. The user can see that the connection is insecure, but crucially there is no way of knowing whether the connection should be secure. HSTS addresses this problem by informing the browser that connections to the site should always use TLS/SSL. See also RFC 6797. -
HTTP 严格传输安全(HSTS,HTTP Strict Transport Security)是一套网页安全策略机制,它通过
Strict-Transport-Security
头交互。 HSTS 能修复的安全漏洞中最重要的是 SSL 剥离攻击(中间人攻击的一种)。SSL 剥离攻击的主要原理是通过把一个 安全的 HTTPS 连接 无痕迹地转化成 明文的 HTTP 连接。用户能看到连接是不安全的,但是没有任何方式去验证该连接是否应该安全。HSTS 尝试通过知会浏览器该网站必须始终使用 TLS/SSL 来解决这个问题。参考 RFC 6797 。
Custom Headers
自定义头域
Sometimes you may need to model a custom header type which is not part of HTTP and still be able to use it as convenient as is possible with the built-in types.
有时你需要建立自定义的头域类型,即使它们不是 HTTP 的一部分,又希望像内置类型一样方便使用。
Because of the number of ways one may interact with headers (i.e. try to matchconvert a CustomHeader
CustomHeader
againstto a RawHeader
RawHeader
or the other way around etc), a helper traitclasses for custom Header types and their companions classes are provided by Akka HTTP. Thanks to extending ModeledCustomHeader
ModeledCustomHeader
instead of the plain CustomHeader
CustomHeader
such header can be matchedthe following methods are at your disposal:
因为可能有各种与头域类型的互动(例如:尝试 模式匹配转换 一个 CustomHeader
CustomHeader
到 RawHeader
RawHeader
类型, 或者倒过来),Akka HTTP 提供了用于自定义 Header 类型 及其伴身类 的辅助 trait类。 由于扩展了 ModeledCustomHeader
ModeledCustomHeader
而不是普通的 CustomHeader
CustomHeader
,@scala[因此可以模式匹配此类头域]你可以使用以下方法:
- Scala
-
final class ApiTokenHeader(token: String) extends ModeledCustomHeader[ApiTokenHeader] { override def renderInRequests = true override def renderInResponses = true override val companion = ApiTokenHeader override def value: String = token } object ApiTokenHeader extends ModeledCustomHeaderCompanion[ApiTokenHeader] { override val name = "apiKey" override def parse(value: String) = Try(new ApiTokenHeader(value)) }
- Java
-
public static class ApiTokenHeader extends ModeledCustomHeader { ApiTokenHeader(String name, String value) { super(name, value); } public boolean renderInResponses() { return false; } public boolean renderInRequests() { return false; } } static class ApiTokenHeaderFactory extends ModeledCustomHeaderFactory<ApiTokenHeader> { public String name() { return "apiKey"; } @Override public ApiTokenHeader parse(String value) { return new ApiTokenHeader(name(), value); } }
Which allows this CustomHeadermodeled custom header to be used in the following scenarios:
使得 CustomHeader建立的自定义头域 可以在以下场景使用。
- Scala
-
val ApiTokenHeader(t1) = ApiTokenHeader("token") t1 should ===("token") val RawHeader(k2, v2) = ApiTokenHeader("token") k2 should ===("apiKey") v2 should ===("token") // will match, header keys are case insensitive val ApiTokenHeader(v3) = RawHeader("APIKEY", "token") v3 should ===("token") intercept[MatchError] { // won't match, different header name val ApiTokenHeader(v4) = DifferentHeader("token") } intercept[MatchError] { // won't match, different header name val RawHeader("something", v5) = DifferentHeader("token") } intercept[MatchError] { // won't match, different header name val ApiTokenHeader(v6) = RawHeader("different", "token") }
- Java
-
final ApiTokenHeaderFactory apiTokenHeaderFactory = new ApiTokenHeaderFactory(); final ApiTokenHeader token = apiTokenHeaderFactory.create("token"); assertEquals("token", token.value()); final HttpHeader header = apiTokenHeaderFactory.create("token"); assertEquals("apiKey", header.name()); assertEquals("token", header.value()); final Optional<ApiTokenHeader> fromRaw = apiTokenHeaderFactory .from(RawHeader.create("apiKey", "token")); assertTrue("Expected a header", fromRaw.isPresent()); assertEquals("apiKey", fromRaw.get().name()); assertEquals("token", fromRaw.get().value()); // will match, header keys are case insensitive final Optional<ApiTokenHeader> fromRawUpper = apiTokenHeaderFactory .from(RawHeader.create("APIKEY", "token")); assertTrue("Expected a header", fromRawUpper.isPresent()); assertEquals("apiKey", fromRawUpper.get().name()); assertEquals("token", fromRawUpper.get().value()); // won't match, different header name final Optional<ApiTokenHeader> wrong = apiTokenHeaderFactory .from(RawHeader.create("different", "token")); assertFalse(wrong.isPresent());
Including usage within the header directives like in the following headerValuePF example:
包括用于头域指令,就像以面的 headerValuePF 示例:
- Scala
-
def extractFromCustomHeader = headerValuePF { case t @ ApiTokenHeader(token) => s"extracted> $t" case raw: RawHeader => s"raw> $raw" } val routes = extractFromCustomHeader { s => complete(s) } Get().withHeaders(RawHeader("apiKey", "TheKey")) ~> routes ~> check { status should ===(StatusCodes.OK) responseAs[String] should ===("extracted> apiKey: TheKey") } Get().withHeaders(RawHeader("somethingElse", "TheKey")) ~> routes ~> check { status should ===(StatusCodes.OK) responseAs[String] should ===("raw> somethingElse: TheKey") } Get().withHeaders(ApiTokenHeader("TheKey")) ~> routes ~> check { status should ===(StatusCodes.OK) responseAs[String] should ===("extracted> apiKey: TheKey") }
- Java
-
import akka.http.javadsl.server.Directives; import static akka.http.javadsl.server.Directives.headerValuePF; final ApiTokenHeaderFactory apiTokenHeaderFactory = new ApiTokenHeaderFactory(); final PartialFunction<HttpHeader, String> extractFromCustomHeader = new JavaPartialFunction<HttpHeader, String>() { @Override public String apply(HttpHeader header, boolean isCheck) throws Exception { if (isCheck) return null; return apiTokenHeaderFactory.from(header) .map(apiTokenHeader -> "extracted> " + apiTokenHeader) .orElseGet(() -> "raw> " + header); } }; final Route route = headerValuePF(extractFromCustomHeader, Directives::complete); testRoute(route) .run(HttpRequest.GET("/").addHeader(RawHeader.create("apiKey", "TheKey"))) .assertStatusCode(StatusCodes.OK) .assertEntity("extracted> apiKey: TheKey"); testRoute(route) .run(HttpRequest.GET("/").addHeader(RawHeader.create("somethingElse", "TheKey"))) .assertStatusCode(StatusCodes.OK) .assertEntity("raw> somethingElse: TheKey"); testRoute(route) .run(HttpRequest.GET("/").addHeader(apiTokenHeaderFactory.create("TheKey"))) .assertStatusCode(StatusCodes.OK) .assertEntity("extracted> apiKey: TheKey");
When defining custom headers, it is better to extend ModeledCustomHeader
ModeledCustomHeader
instead of its parent CustomHeader
CustomHeader
. Custom headers that extend ModeledCustomHeader
ModeledCustomHeader
automatically comply with the pattern matching semantics that usually apply to built-in types (such as matching a custom header against a RawHeader
RawHeader
in routing layers of Akka HTTP applications).
当定义自定义头域时,最好扩展 ModeledCustomHeader
ModeledCustomHeader
而不是父类型 CustomHeader
CustomHeader
。 自定义头扩展 ModeledCustomHeader
ModeledCustomHeader
会自动遵循通常用于内置类型的模式匹配语义(例如,在 Akka HTTP 应用程序的路由层对自定义头与 RawHeader
RawHeader
进行匹配)。
Implement ModeledCustomHeader
ModeledCustomHeader
and ModeledCustomHeaderFactory
instead of CustomHeader
CustomHeader
to be able to use the convenience methods that allow parsing the custom user-defined header from HttpHeader
HttpHeader
.
Parsing / Rendering
解析 / 渲染
Parsing and rendering of HTTP data structures is heavily optimized and for most types there’s currently no public API provided to parse (or render to) Strings or byte arrays.
解析和渲染 HTTP 数据结构已经被重试优化过了,而且对于大部分类型并没有公开 API 用于 解析(或渲染到)字符串或字节数组。
Various parsing and rendering settings are available to tweak in the configuration under akka.http.client[.parsing]
, akka.http.server[.parsing]
and akka.http.host-connection-pool[.client.parsing]
, with defaults for all of these being defined in the akka.http.parsing
configuration section.
在配置 akka.http.client[.parsing]
、akka.http.server[.parsing]
和 akka.http.host-connection-pool[.client.parsing]
有各种解析和渲染设置可用于调整,它们所有的预设值都在 akka.http.parsing
部分定义好了。
For example, if you want to change a parsing setting for all components, you can set the akka.http.parsing.illegal-header-warnings = off
value. However this setting can be still overridden by the more specific sections, like for example akka.http.server.parsing.illegal-header-warnings = on
.
例如:如果你想改变所有组件的解析设置,你可以设置 akka.http.parsing.illegal-header-warnings = off
。然而,这个设置能被更特殊的部分覆盖, 例如 akka.http.server.parsing.illegal-header-warnings = on
。
In this case both client
and host-connection-pool
APIs will see the setting off
, however the server will see on
.
这种情况下 client
和 host-connection-pool
API将看见设置 off
,而服务器端将看见设置 on
。
In the case of akka.http.host-connection-pool.client
settings, they default to settings set in akka.http.client
, and can override them if needed. This is useful, since both client
and host-connection-pool
APIs, such as the Client API Http().outgoingConnection
Http.get(sys).outgoingConnection
or the Host Connection Pool APIs Http().singleRequest
Http.get(sys).singleRequest
or Http().superPool
Http.get(sys).superPool
, usually need the same settings, however the server
most likely has a very different set of settings.
对于 akka.http.host-connection-pool.client
的设置,它们的预设值在 akka.http.client
,如果需要可以覆盖它们。既然 client
和 host-connection-pool
的 API,例如 Http().outgoingConnection
Http.get(sys).outgoingConnection
或者基于主机的连接池 API,例如 Http().superPool
Http.get(sys).superPool
通常共享某些参数设置,而服务器端则很大可能用一套不同的设置,这样的设计就很有用了。
Registering Custom Media Types
注册自定义媒体类型
Akka HTTP predefines
predefines
most commonly encountered media types and emits them in their well-typed form while parsing http messages. Sometimes you may want to define a custom media type and inform the parser infrastructure about how to handle these custom media types, e.g. that application/custom
is to be treated as NonBinary
with WithFixedCharset
. To achieve this you need to register the custom media type in the server’s settings by configuring ParserSettings
ParserSettings
like this:
Akka HTTP 预定义
预定义
了最常用的媒体类型,并在解析 http 消息时以正确的类型提取它们。 有时你也许想定义一个自定义媒体类型并指引语法分析工具如何处理这些新的媒体类型。例如,application/custom
被看作 NonBinary
以及 WithFixedCharset
的类型。你需要在服务器的 ParserSettings
ParserSettings
中注册这些自定义类型:
- Scala
-
// similarly in Java: `akka.http.javadsl.settings.[...]` import akka.http.scaladsl.settings.ParserSettings import akka.http.scaladsl.settings.ServerSettings // define custom media type: val utf8 = HttpCharsets.`UTF-8` val `application/custom`: WithFixedCharset = MediaType.customWithFixedCharset("application", "custom", utf8) // add custom media type to parser settings: val parserSettings = ParserSettings(system).withCustomMediaTypes(`application/custom`) val serverSettings = ServerSettings(system).withParserSettings(parserSettings) val routes = extractRequest { r => complete(r.entity.contentType.toString + " = " + r.entity.contentType.getClass) } Http().bindAndHandle(routes, host, port, settings = serverSettings) - Java
-
import static akka.http.javadsl.server.Directives.complete; import static akka.http.javadsl.server.Directives.extractRequest; // Define custom media type: final MediaType.WithFixedCharset applicationCustom = MediaTypes.customWithFixedCharset("application", "custom", // The new Media Type name HttpCharsets.UTF_8, // The charset used new HashMap<>(), // Empty parameters false); // No arbitrary subtypes are allowed // Add custom media type to parser settings: final ParserSettings parserSettings = ParserSettings.create(system) .withCustomMediaTypes(applicationCustom); final ServerSettings serverSettings = ServerSettings.create(system) .withParserSettings(parserSettings); final Route route = extractRequest(req -> complete(req.entity().getContentType().toString() + " = " + req.entity().getContentType().getClass()) ); final CompletionStage<ServerBinding> binding = Http.get(system) .bindAndHandle(route.flow(system, materializer), ConnectHttp.toHost(host, 0), serverSettings, system.log(), materializer);
You may also want to read about MediaType Registration trees, in order to register your vendor specific media types in the right style / place.
你也许也想读下 MediaType 的 注册树,以便正确的注册开发商专属的媒体类型。
Registering Custom Status Codes
注册自定义状态码
Similarly to media types, Akka HTTP predefines
predefines
well-known status codes, however sometimes you may need to use a custom one (or are forced to use an API which returns custom status codes). Similarly to the media types registration, you can register custom status codes by configuring ParserSettings
ParserSettings
like this:
类似媒体类型,Akka HTTP 预定义
预定义
了已知的状态码,然而,有时你也需要使用自定义的状态码(或被迫使用返回自定义状态码的 API)。 类似于媒体类型的注册,你能通过配置 ParserSettings
ParserSettings
来注册自定状态码:
- Scala
-
// similarly in Java: `akka.http.javadsl.settings.[...]` import akka.http.scaladsl.settings.{ ParserSettings, ServerSettings } // define custom status code: val LeetCode = StatusCodes.custom(777, "LeetCode", "Some reason", isSuccess = true, allowsEntity = false) // add custom method to parser settings: val parserSettings = ParserSettings(system).withCustomStatusCodes(LeetCode) val serverSettings = ServerSettings(system).withParserSettings(parserSettings) val clientConSettings = ClientConnectionSettings(system).withParserSettings(parserSettings) val clientSettings = ConnectionPoolSettings(system).withConnectionSettings(clientConSettings) val routes = complete(HttpResponse(status = LeetCode)) // use serverSettings in server: val binding = Http().bindAndHandle(routes, host, port, settings = serverSettings) // use clientSettings in client: val request = HttpRequest(uri = s"http://$host:$port/") val response = Http().singleRequest(request, settings = clientSettings) // futureValue is a ScalaTest helper: response.futureValue.status should ===(LeetCode)
- Java
-
import static akka.http.javadsl.server.Directives.complete; import static akka.http.javadsl.server.Directives.extractRequest; // Define custom status code: final StatusCode leetCode = StatusCodes.custom(777, // Our custom status code "LeetCode", // Our custom reason "Some reason", // Our custom default message true, // It should be considered a success response false);// Does not allow entities // Add custom method to parser settings: final ParserSettings parserSettings = ParserSettings.create(system) .withCustomStatusCodes(leetCode); final ServerSettings serverSettings = ServerSettings.create(system) .withParserSettings(parserSettings); final ClientConnectionSettings clientConSettings = ClientConnectionSettings.create(system) .withParserSettings(parserSettings); final ConnectionPoolSettings clientSettings = ConnectionPoolSettings.create(system) .withConnectionSettings(clientConSettings); final Route route = extractRequest(req -> complete(HttpResponse.create().withStatus(leetCode)) ); // Use serverSettings in server: final CompletionStage<ServerBinding> binding = Http.get(system) .bindAndHandle(route.flow(system, materializer), ConnectHttp.toHost(host, 0), serverSettings, system.log(), materializer); final ServerBinding serverBinding = binding.toCompletableFuture().get(); final int port = serverBinding.localAddress().getPort(); // Use clientSettings in client: final HttpResponse response = Http.get(system) .singleRequest(HttpRequest .GET("http://" + host + ":" + port + "/"), ConnectionContext.https(SSLContext.getDefault()), clientSettings, system.log(), materializer) .toCompletableFuture() .get(); // Check we get the right code back assertEquals(leetCode, response.status());
Registering Custom HTTP Method
注册自定义 HTTP 方法
Akka HTTP also allows you to define custom HTTP methods, other than the well-known methods predefined
predefined
in Akka HTTP. To use a custom HTTP method, you need to define it, and then add it to parser settings like below:
Akka HTTP 也允许你自定义 HTTP 方法,既除了 Akka HTTP 里 预定义
预定义
了的已知的方法。要使用自定义 HTTP 方法,你需要定义它,并向下面一样添加到解析设置里:
- Scala
-
import akka.http.scaladsl.settings.{ ParserSettings, ServerSettings } // define custom method type: val BOLT = HttpMethod.custom("BOLT", safe = false, idempotent = true, requestEntityAcceptance = Expected) // add custom method to parser settings: val parserSettings = ParserSettings(system).withCustomMethods(BOLT) val serverSettings = ServerSettings(system).withParserSettings(parserSettings) val routes = extractMethod { method => complete(s"This is a ${method.name} method request.") } val binding = Http().bindAndHandle(routes, host, port, settings = serverSettings) val request = HttpRequest(BOLT, s"http://$host:$port/", protocol = `HTTP/1.1`)
- Java
-
import static akka.http.javadsl.server.Directives.complete; import static akka.http.javadsl.server.Directives.route; import static akka.http.javadsl.server.Directives.extractMethod; // define custom method type: HttpMethod BOLT = HttpMethods.custom("BOLT", false, true, Expected); // add custom method to parser settings: final ParserSettings parserSettings = ParserSettings.create(system).withCustomMethods(BOLT); final ServerSettings serverSettings = ServerSettings.create(system).withParserSettings(parserSettings); final Route routes = concat( extractMethod( method -> complete( "This is a " + method.name() + " request.") ) ); final Flow<HttpRequest, HttpResponse, NotUsed> handler = routes.flow(system, materializer); final Http http = Http.get(system); final CompletionStage<ServerBinding> binding = http.bindAndHandle( handler, ConnectHttp.toHost(host, port), serverSettings, loggingAdapter, materializer); HttpRequest request = HttpRequest.create() .withUri("http://" + host + ":" + Integer.toString(port)) .withMethod(BOLT) .withProtocol(HTTP_1_1); CompletionStage<HttpResponse> response = http.singleRequest(request, materializer);