Dart服务器端 shelf包 原

介绍

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

属性

handler → Handler

  • 将此cascase作为单个处理程序公开
  • read-only

hashCode → int

runtimeType → Type

方法

add(Handler handler) → Cascade

  • 返回一个新的cascase,并将处理程序添加到末尾

noSuchMethod(Invocation invocation) → dynamic

toString() → String

Pipeline

帮助程序,可以轻松组成一组中间件和一个处理程序。

var handler = const Pipeline()
    .addMiddleware(loggingMiddleware)
    .addMiddleware(cachingMiddleware)
    .addHandler(application);

构造函数

Pipeline()

  • const

属性

middleware → Middleware

  • 将此中间件pipeline公开为单个中间件实例

hashCode → int

runtimeType → Type

方法

addHandler(Handler handler) → Handler

  • 如果pipeline中的所有中间件都已通过请求,则返回一个新的Handler,其中handler作为Request的最终处理器。

addMiddleware(Middleware middleware) → Pipeline

  • 返回一个新的Pipeline,其中间件添加到现有的中间件集中

noSuchMethod(Invocation invocation) → dynamic

toString() → String

Request

表示要由Shelf应用程序处理的HTTP请求。

构造函数

Request(String method, Uri requestedUri, { String protocolVersion, Map<StringString> headers, String handlerPath, Uri url, dynamic body, Encoding encoding, Map<StringObject> context, void onHijack(void hijack(StreamChannel<List<int>> channel)) })

创建一个新的Request

属性

canHijack → bool

  • 此请求是否可以被劫持
  • read-only

handlerPath → String

  • 当前处理程序的URL路径
  • final

ifModifiedSince → DateTime

  • 如果此值为非null并且自此日期和时间以来所请求的资源未修改,则服务器应返回304 Not Modified响应
  • read-only

method → String

  • HTTP请求方法,例如“GET”或“POST”
  • final

protocolVersion → String

  • 请求中使用的HTTP协议版本,“1.0”或“1.1”。
  • final

requestedUri → Uri

  • 原始的Uri请求
  • final

url → Uri

  • 从当前处理程序到请求的资源的URL路径,相对于handlerPath,以及任何查询参数
  • final

contentLength → int

  • 标题中content-length字段的内容
  • read-only, inherited

context → Map<StringObject>

  • 中间件和处理程序可以使用的额外上下文
  • final, inherited

encoding → Encoding

  • 消息正文的编码
  • read-only, inherited

hashCode → int

headers → Map<StringString>

  • HTTP标头
  • final, inherited

isEmpty → bool

  • 如果为true,则read返回的流将不会发出任何字节
  • read-only, inherited

mimeType → String

  • 消息的MIME类型
  • read-only, inherited

runtimeType → Type

方法

change({Map<StringString> headers, Map<StringObject> context, String path, dynamic body }) → Request

  • 通过复制现有值并应用指定的更改来创建新的请求

hijack(void callback(StreamChannel<List<int>> channel)) → void

  • 控制底层请求套接字

noSuchMethod(Invocation invocation) → dynamic

read() → Stream<List<int>>

  • 返回表示正文的Stream
  • inherited

readAsString([Encoding encoding ]) → Future<String>

  • 返回包含Body作为String的Future
  • inherited

toString() → String

Response

处理程序返回的响应

构造函数

Response(int statusCode, { dynamic body, Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 使用给定的statusCode构造HTTP响应

Response.forbidden(dynamic body, { Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 构造403 Forbidden响应

Response.found(dynamic location, { dynamic body, Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 构造302 Found响应

Response.internalServerError({dynamic body, Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 构造500内部服务器错误响应

Response.movedPermanently(dynamic location, { dynamic body, Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 构造301 Moved Permanently响应

Response.notFound(dynamic body, { Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 构造404 Not Found响应

Response.notModified({Map<StringString> headers, Map<StringObject> context })

  • 构造304 Not Modified响应

Response.ok(dynamic body, { Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 构造200 OK响应

Response.seeOther(dynamic location, { dynamic body, Map<StringString> headers, Encoding encoding, Map<StringObject> context })

  • 构造一个303见其他响应

属性

expires → DateTime

  • 应将响应数据视为过时的日期和时间
  • read-only

lastModified → DateTime

  • 上次修改响应数据源的日期和时间
  • read-only

statusCode → int

  • 响应的HTTP状态代码
  • final

contentLength → int

  • 标题中content-length字段的内容
  • read-only, inherited

context → Map<StringObject>

  • 中间件和处理程序可以使用的额外上下文
  • final, inherited

encoding → Encoding

  • 消息正文的编码
  • read-only, inherited

hashCode → int

headers → Map<StringString>

  • HTTP标头
  • final, inherited

isEmpty → bool

  • 如果为true,则read返回的流将不会发出任何字节
  • read-only, inherited

mimeType → String

  • 消息的MIME类型
  • read-only, inherited

runtimeType → Type

方法

change({Map<StringString> headers, Map<StringObject> context, dynamic body }) → Response

  • 通过复制现有值并应用指定的更改来创建新的响应

noSuchMethod(Invocation invocation) → dynamic

read() → Stream<List<int>>

  • 返回表示正文的Stream
  • inherited

readAsString([Encoding encoding ]) → Future<String>

  • 返回包含Body作为String的Future
  • inherited

toString() → String

Server 

具有具体URL的适配器

“适配器”的最基本定义包括将传入请求传递给处理程序并将其响应传递给某个外部客户端的任何函数,但是,在实践中,大多数适配器也是服务器 - 也就是说,它们正在处理对某个已知URL进行的请求

此接口以一般方式表示这些服务器。 它对于编写需要知道自己的URL而不将该代码紧密耦合到单个服务器实现的代码很有用

这个接口有两个内置的实现。 您可以使用IOServer创建由dart:io支持的服务器,或者您可以使用ServerHandler创建由普通Handler支持的服务器

此接口的实现负责确保成员按照文档的方式工作

Implemented by IOServer

构造函数

Server()

属性

url → Uri

  • 服务器的URL
  • read-only

hashCode → int

runtimeType → Type

方法

close() → Future

  • 关闭服务器并返回在释放所有资源时完成的Future

mount(Handler handler) → void

  • 将处理程序挂载为此服务器的基本处理程序

noSuchMethod(Invocation invocation) → dynamic

  • 访问不存在的方法或属性时调用

toString() → String

  • 返回此对象的字符串表示形式

ServerHandler

连接的服务器和处理程序对

处理程序的请求一旦可用就会发送到服务器的挂载处理程序。这用于公开实际上是较大URL空间的一部分的虚拟服务器。 构造函数

ServerHandler(Uri url, { dynamic onClose() })

  • 使用给定的URL和Handler创建一个新的连接的服务器对

属性

handler → Handler

  • 处理程序
  • read-only

server → Server

  • 服务器
  • read-only

hashCode → int

runtimeType → Type

方法

noSuchMethod(Invocation invocation) → dynamic

toString() → String

属性

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

构造函数

HijackException()

const

属性

hashCode → int

runtimeType → Type

方法

toString() → String

noSuchMethod(Invocation invocation) → dynamic

shelf_io

IOServer

由dart:io HttpServer支持的服务器

Implements Server

构造函数

IOServer(HttpServer server)

属性

server → HttpServer

  • 底层的HttpServer
  • final

url → Uri

  • 服务器的URL
  • read-only

hashCode → int

runtimeType → Type

方法

close() → Future

  • 关闭服务器并返回在释放所有资源时完成的Future

mount(Handler handler) → void

  • 将处理程序挂载为此服务器的基本处理程序

noSuchMethod(Invocation invocation) → dynamic

toString() → String

静态方法

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请求流。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博客

C#简单的面试题目(六)

76.HashMap和Hashtable的区别。 答:HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于H...

882
来自专栏数据之美

线上服务 CPU 100%?一键定位 so easy!

背景 经常做后端服务开发的同学,或多或少都遇到过 CPU 负载特别高的问题。尤其是在周末或大半夜,突然群里有人反馈线上机器负载特别高,不熟悉定位流程和思路的同学...

5138
来自专栏分布式系统和大数据处理

.Net Remoting(分离服务程序实现) - Part.3

在上面Remoting基本操作的范例中,我们发现了这样一个情况:即是 客户应用程序 仍然需要引用 服务程序集(ServerAssembly),因为它需要Demo...

991
来自专栏项勇

笔记12 | 复习Volley(一)基本概念和用法

1854
来自专栏技术之路

【swift学习笔记】二.页面转跳数据回传

上一篇我们介绍了页面转跳:【swift学习笔记】一.页面转跳的条件判断和传值 这一篇说一下如何把数据回传回父页面,如下图所示,这个例子很简单,只是把传过去的数据...

2178
来自专栏python学习路

八、线程和进程 什么是线程(thread)?什么是进程(process)? 线程和进程的区别?Python GIL(Global Interpreter Lock)全局解释器锁

什么是线程(thread)? 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一...

4807
来自专栏Albert陈凯

2018-05-03 Java高级面试题及答案各自的子类比较对比一:

2025
来自专栏进击的程序猿

php异常处理 之 BooBoo库介绍

本文介绍php开源库BooBoo,是一个处理php异常和错误的开源库,通过简单的分析代码,我们知道了实际项目中怎么正确的设置错误和异常。

1032
来自专栏沈唁志

在Linux中vim的用法

2182
来自专栏ascii0x03的安全笔记

Linux下ls命令显示符号链接权限为777的探索

Linux下ls命令显示符号链接权限为777的探索                                                ——深入ls、...

1K5

扫码关注云+社区

领取腾讯云代金券