介绍
Shelf可以轻松创建和组合Web服务器和Web服务器的一部分。 怎么样?
- 暴露一小部分简单类型。
- 将服务器逻辑映射为一个简单的函数:请求的单个参数,响应是返回值。
- 简单地混合和匹配同步和异步处理。
- 灵活地返回具有相同模型的简单字符串或字节流。
例子
参见example / example.dart
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
void main() {
var handler = const shelf.Pipeline().addMiddleware(shelf.logRequests())
.addHandler(_echoRequest);
io.serve(handler, 'localhost', 8080).then((server) {
print('Serving at http://${server.address.host}:${server.port}');
});
}
shelf.Response _echoRequest(shelf.Request request) {
return new shelf.Response.ok('Request for "${request.url}"');
}
处理程序和中间件
handler是处理shelf.Request并返回shelf.Response的任何函数。它可以处理请求本身 - 例如,在文件系统上查找请求的URI的静态文件服务器 - 或者它可以进行一些处理并将其转发到另一个处理程序 - 例如,打印有关信息的记录器 请求和对命令行的响应。
后一种处理程序称为“中间件”,因为它位于服务器堆栈的中间。中间件可以被认为是一个函数,它接受一个处理程序并将其包装在另一个处理程序中以提供其他功能。Shelf应用程序通常由多层中间件组成,中间有一个或多个处理程序; shelf.Pipeline类使这种应用程序易于构建。
一些中间件也可以采用多个处理程序,并为每个请求调用其中一个或多个。例如,路由中间件可能会根据请求的URI或HTTP方法选择要调用的处理程序,而级联中间件可能会按顺序调用每个处理程序,直到返回成功的响应。
在处理程序之间路由请求的中间件应确保更新每个请求的handlerPath和url。 这允许内部处理程序知道它们在应用程序中的位置,以便它们可以正确地执行自己的路由。 这可以使用Request.change()轻松完成:
// 在一个虚构的路由中间件中......
var component = request.url.pathComponents.first;
var handler = _handlers[component];
if (handler == null) return new Response.notFound(null);
// 创建一个与此类似的新请求,但改为使用[component]之后的任何URL。
return handler(request.change(script: component));
适配器
适配器是创建shelf.Request对象的任何代码,将它们传递给处理程序,并处理生成的shelf.Response。在大多数情况下,适配器转发来自底层HTTP服务器的请求和响应; shelf_io.serve就是这种适配器。适配器也可能使用window.location和window.history在浏览器中合成HTTP请求,或者它可能直接将请求从HTTP客户端传递到Shelf处理程序。
API要求
适配器必须处理来自处理程序的所有错误,包括返回null响应的处理程序。如果可能的话,它应该将每个错误打印到控制台,然后就像处理程序返回500响应一样。适配器可能包含500响应的正文数据,但此正文数据不得包含有关发生的错误的信息。这可确保默认情况下意外错误不会导致生产中的内部信息泄露; 如果用户想要返回详细的错误描述,他们应该明确包含中间件来执行此操作。
适配器应确保处理程序抛出的异步错误不会导致应用程序崩溃,即使future链未报告它们。具体来说,不应将这些错误传递给根区域的错误处理程序; 但是,如果适配器在另一个错误区域内运行,则应允许将这些错误传递到该区域。以下函数可用于捕获单一错误否则那将是顶级的:
/// 运行[callback] 并且捕获任何顶级错误.
///
/// 如果在非根错误区域中调用[this],它将只运行[callback]
/// 并返回结果。 否则,它将使用[runZoned]捕获任何错误并将它们传递给[onError]。
catchTopLevelErrors(callback(), void onError(error, StackTrace stackTrace)) {
if (Zone.current.inSameErrorZone(Zone.ROOT)) {
return runZoned(callback, onError: onError);
} else {
return callback();
}
}
知道自己的URL的适配器应该提供Server接口的实现。
Request 要求
实现适配器时,必须遵循一些规则。适配器不能将url或handlerPath参数传递给新的shelf.Request; 它应该只传递requestedUri。如果它传递了context参数,则所有Key必须以适配器的包名称开头,后跟句点。如果收到多个具有相同名称的标头,则适配器必须按照RFC 2616第4.2节将它们折叠为用逗号分隔的单个标头。
如果基础请求使用分块传输编码,则适配器必须先解码主体,然后再将其传递给新的shelf.Request,并应删除Transfer-Encoding标头。这可以确保当且仅当标头声明它们是时,才会对邮件正文进行分块。
Response 要求
适配器不得为响应添加或修改任何实体标头。
如果以下条件均不为真,则适配器必须将分块传输编码应用于响应的正文并将其Transfer-Encoding标头设置为chunked:
- 状态代码小于200,或等于204或304。
- 提供Content-Length标头。
- Content-Type标头指示MIME类型multipart / byteranges。
- Transfer-Encoding标头设置为identity以外的任何其他标头。
如果底层服务器没有手动实现,那么适配器可能会发现[addChunkedEncoding()] [addChunkedEncoding]中间件对实现此行为很有用。
响应HEAD请求时,适配器不得发出实体主体。 否则,它不应以任何方式修改实体主体。
默认情况下,适配器应在响应的Server标头中包含有关其自身的信息。 如果处理程序返回带有Server标头集的响应,则该响应必须优先于适配器的默认标头。
适配器应包含Date标头以及处理程序返回响应的时间。 如果处理程序返回带有Date标头集的响应,则必须优先。
包
shelf
类
Cascade
一个帮助程序,它按顺序调用多个处理程序并返回第一个可接受的响应。[...]
默认情况下,如果响应的状态不是404或405,则认为该响应是可接受的; 其他状态表明处理程序理解请求。
如果所有处理程序都返回不可接受的响应,则将返回最终响应。
var handler = new Cascade()
.add(webSocketHandler)
.add(staticFileHandler)
.add(application)
.handler;
构造函数
Cascade({Iterable<int> statusCodes, bool shouldCascade(Response response) })
创建一个新的空的cascase
属性
- 将此cascase作为单个处理程序公开
- read-only
方法
add(Handler handler) → Cascade
- 返回一个新的cascase,并将处理程序添加到末尾
noSuchMethod(Invocation invocation) → dynamic
Pipeline
帮助程序,可以轻松组成一组中间件和一个处理程序。
var handler = const Pipeline()
.addMiddleware(loggingMiddleware)
.addMiddleware(cachingMiddleware)
.addHandler(application);
构造函数
Pipeline()
- const
属性
- 将此中间件pipeline公开为单个中间件实例
方法
addHandler(Handler handler) → Handler
- 如果pipeline中的所有中间件都已通过请求,则返回一个新的Handler,其中handler作为Request的最终处理器。
addMiddleware(Middleware middleware) → Pipeline
- 返回一个新的Pipeline,其中间件添加到现有的中间件集中
noSuchMethod(Invocation invocation) → dynamic
Request
表示要由Shelf应用程序处理的HTTP请求。
构造函数
Request(String method, Uri requestedUri, { String protocolVersion, Map<String, String> headers, String handlerPath, Uri url, dynamic body, Encoding encoding, Map<String, Object> context, void onHijack(void hijack(StreamChannel<List<int>> channel)) })
创建一个新的Request
属性
- 此请求是否可以被劫持
- read-only
- 当前处理程序的URL路径
- final
- 如果此值为非null并且自此日期和时间以来所请求的资源未修改,则服务器应返回304 Not Modified响应
- read-only
- HTTP请求方法,例如“GET”或“POST”
- final
- 请求中使用的HTTP协议版本,“1.0”或“1.1”。
- final
- 原始的Uri请求
- final
- 从当前处理程序到请求的资源的URL路径,相对于handlerPath,以及任何查询参数
- final
contentLength → int
- 标题中content-length字段的内容
- read-only, inherited
- 中间件和处理程序可以使用的额外上下文
- final, inherited
encoding → Encoding
- 消息正文的编码
- read-only, inherited
- HTTP标头
- final, inherited
isEmpty → bool
- 如果为true,则read返回的流将不会发出任何字节
- read-only, inherited
mimeType → String
- 消息的MIME类型
- read-only, inherited
方法
change({Map<String, String> headers, Map<String, Object> context, String path, dynamic body }) → Request
- 通过复制现有值并应用指定的更改来创建新的请求
hijack(void callback(StreamChannel<List<int>> channel)) → void
- 控制底层请求套接字
noSuchMethod(Invocation invocation) → dynamic
- 返回表示正文的Stream
- inherited
readAsString([Encoding encoding ]) → Future<String>
- 返回包含Body作为String的Future
- inherited
Response
处理程序返回的响应
构造函数
Response(int statusCode, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 使用给定的statusCode构造HTTP响应
Response.forbidden(dynamic body, { Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 构造403 Forbidden响应
Response.found(dynamic location, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 构造302 Found响应
Response.internalServerError({dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 构造500内部服务器错误响应
Response.movedPermanently(dynamic location, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 构造301 Moved Permanently响应
Response.notFound(dynamic body, { Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 构造404 Not Found响应
Response.notModified({Map<String, String> headers, Map<String, Object> context })
- 构造304 Not Modified响应
Response.ok(dynamic body, { Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 构造200 OK响应
Response.seeOther(dynamic location, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
- 构造一个303见其他响应
属性
- 应将响应数据视为过时的日期和时间
- read-only
- 上次修改响应数据源的日期和时间
- read-only
- 响应的HTTP状态代码
- final
contentLength → int
- 标题中content-length字段的内容
- read-only, inherited
- 中间件和处理程序可以使用的额外上下文
- final, inherited
encoding → Encoding
- 消息正文的编码
- read-only, inherited
- HTTP标头
- final, inherited
isEmpty → bool
- 如果为true,则read返回的流将不会发出任何字节
- read-only, inherited
mimeType → String
- 消息的MIME类型
- read-only, inherited
方法
change({Map<String, String> headers, Map<String, Object> context, dynamic body }) → Response
- 通过复制现有值并应用指定的更改来创建新的响应
noSuchMethod(Invocation invocation) → dynamic
- 返回表示正文的Stream
- inherited
readAsString([Encoding encoding ]) → Future<String>
- 返回包含Body作为String的Future
- inherited
Server
具有具体URL的适配器
“适配器”的最基本定义包括将传入请求传递给处理程序并将其响应传递给某个外部客户端的任何函数,但是,在实践中,大多数适配器也是服务器 - 也就是说,它们正在处理对某个已知URL进行的请求
此接口以一般方式表示这些服务器。 它对于编写需要知道自己的URL而不将该代码紧密耦合到单个服务器实现的代码很有用
这个接口有两个内置的实现。 您可以使用IOServer创建由dart:io支持的服务器,或者您可以使用ServerHandler创建由普通Handler支持的服务器
此接口的实现负责确保成员按照文档的方式工作
Implemented by IOServer
构造函数
Server()
属性
- 服务器的URL
- read-only
方法
- 关闭服务器并返回在释放所有资源时完成的Future
- 将处理程序挂载为此服务器的基本处理程序
noSuchMethod(Invocation invocation) → dynamic
- 访问不存在的方法或属性时调用
- 返回此对象的字符串表示形式
ServerHandler
连接的服务器和处理程序对
处理程序的请求一旦可用就会发送到服务器的挂载处理程序。这用于公开实际上是较大URL空间的一部分的虚拟服务器。
构造函数
ServerHandler(Uri url, { dynamic onClose() })
- 使用给定的URL和Handler创建一个新的连接的服务器对
属性
- 处理程序
- read-only
- 服务器
- read-only
方法
noSuchMethod(Invocation invocation) → dynamic
属性
addChunkedEncodin顶级属性
中间件addChunkedEncoding final
如果以下条件均不属实,中间件将分块传输编码添加到响应中
提供Content-Length标头。 Content-Type标头指示MIME类型multipart / byteranges。Transfer-Encoding标头已包含分块编码
这适用于Shelf适配器而非最终用户
实现
final addChunkedEncoding = createMiddleware(responseHandler: (response) {
if (response.contentLength != null) return response;
if (response.statusCode < 200) return response;
if (response.statusCode == 204) return response;
if (response.statusCode == 304) return response;
if (response.mimeType == 'multipart/byteranges') return response;
// We only check the last coding here because HTTP requires that the chunked
// encoding be listed last.
var coding = response.headers['transfer-encoding'];
if (coding != null && !equalsIgnoreAsciiCase(coding, 'identity')) {
return response;
}
return response.change(
headers: {'transfer-encoding': 'chunked'},
body: chunkedCoding.encoder.bind(response.read()));
})
方法
createMiddleware
Middleware createMiddleware ({
FutureOr<Response> requestHandler(
Request request
),
FutureOr<Response> responseHandler(
Response response
),
FutureOr<Response> errorHandler(
dynamic error,
StackTrace stackTrace
)
})
使用提供的函数创建中间件。
如果提供,requestHandler将收到一个请求。 它可以通过返回Response或Future<Response>来响应请求。对于部分requestHandler也可以返回null,货全部请求被发送到内部处理程序
如果提供,则使用内部处理程序生成的响应调用responseHandler。requestHandler生成的响应不会发送到responseHandler
responseHandler应该返回Response或Future <Response>。 它可以返回它接收的响应参数或创建一个新的Response对象
如果提供,errorHandler会收到内部处理程序抛出的错误。 它不会收到requestHandler或responseHandler抛出的错误,也不会收到HijackExceptions。 它可以返回新响应或抛出错误
实现
Middleware createMiddleware(
{FutureOr<Response> requestHandler(Request request),
FutureOr<Response> responseHandler(Response response),
FutureOr<Response> errorHandler(error, StackTrace stackTrace)}) {
if (requestHandler == null) requestHandler = (request) => null;
if (responseHandler == null) responseHandler = (response) => response;
var onError;
if (errorHandler != null) {
onError = (error, stackTrace) {
if (error is HijackException) throw error;
return errorHandler(error, stackTrace);
};
}
return (Handler innerHandler) {
return (request) {
return new Future.sync(() => requestHandler(request)).then((response) {
if (response != null) return response;
return new Future.sync(() => innerHandler(request))
.then((response) => responseHandler(response), onError: onError);
});
};
};
}
logRequests
Middleware logRequests ({
void logger(
String msg,
bool isError
)
})
中间件打印请求的时间,内部处理程序的已用时间,响应的状态代码和请求URI
如果传递了logger,则会为每个请求调用它。msg参数是一个格式化的字符串,包括请求时间,持续时间,请求方法和请求的路径。抛出异常时,它还包括异常的字符串和堆栈跟踪; 否则,它包括状态代码。isError参数指示消息是否由错误引起
如果未传递logger,则只传递message以进行打印
实现
Middleware logRequests({void logger(String msg, bool isError)}) =>
(innerHandler) {
if (logger == null) logger = _defaultLogger;
return (request) {
var startTime = new DateTime.now();
var watch = new Stopwatch()..start();
return new Future.sync(() => innerHandler(request)).then((response) {
var msg = _getMessage(startTime, response.statusCode,
request.requestedUri, request.method, watch.elapsed);
logger(msg, false);
return response;
}, onError: (error, stackTrace) {
if (error is HijackException) throw error;
var msg = _getErrorMessage(startTime, request.requestedUri,
request.method, watch.elapsed, error, stackTrace);
logger(msg, true);
throw error;
});
};
};
类型定义
Handler
FutureOr<Response> Handler (
Request request
)
处理请求的函数
例如,静态文件处理程序可以从文件系统读取请求的URI,并将其作为Response的主体返回
包装一个或多个其他处理程序以执行前处理或后处理的处理程序称为“中间件”
处理程序可以直接从HTTP服务器接收请求,或者可能已被其他中间件处理过。类似地,响应可以由HTTP服务器直接返回,或者由其他中间件完成进一步处理
Middleware
Handler Middleware (
Handler innerHandler
)
通过包装处理程序创建新Handler的函数
您可以通过将处理程序包装在中间件中来扩展其功能,中间件可以在请求发送到处理程序之前拦截并处理请求,处理程序发送后的响应或者两者都可以。
由于中间件使用处理程序并返回新的处理程序,因此可以将多个中间件实例组合在一起以提供丰富的功能。
中间件的常见用途包括缓存,日志记录和身份验证。
捕获异常的中间件应确保无需修改即可传递HijackExceptions。
可以使用createMiddleware创建一个简单的中间件
异常
HijackException
用于表示请求已被劫持的异常
除了创建可劫持请求的Shelf适配器之外的任何代码都不应捕获此内容。 捕获异常的中间件应确保传递HijackExceptions
另请参见Request.hijack。
Implements Exception
构造函数
const
属性
方法
noSuchMethod(Invocation invocation) → dynamic
shelf_io
类
IOServer
由dart:io HttpServer支持的服务器
Implements Server
构造函数
IOServer(HttpServer server)
属性
- 底层的HttpServer
- final
- 服务器的URL
- read-only
方法
- 关闭服务器并返回在释放所有资源时完成的Future
- 将处理程序挂载为此服务器的基本处理程序
noSuchMethod(Invocation invocation) → dynamic
静态方法
bind(dynamic address, int port, { int backlog }) → Future<IOServer>
- 调用HttpServer.bind并将结果包装在IOServer中
方法
handleRequest(HttpRequest request, Handler handler) → Future
- 使用handler来处理请求
serve(Handler handler, dynamic address, int port, { SecurityContext securityContext, int backlog }) → Future<HttpServer>
- 启动一个侦听指定地址和端口的HttpServer,并将请求发送给处理程序
serveRequests(Stream<HttpRequest> requests, Handler handler) → void
- 提供Http请求流。