天天看點

Dart伺服器端 shelf包介紹例子處理程式和中間件擴充卡API要求Request 要求Response 要求包

2019獨角獸企業重金招聘Python工程師标準>>>

Dart伺服器端 shelf包介紹例子處理程式和中間件擴充卡API要求Request 要求Response 要求包

介紹

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<String, String> headers, String handlerPath, Uri url, dynamic body, Encoding encoding, Map<String, Object> 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<String, Object>

  • 中間件和處理程式可以使用的額外上下文
  • final, inherited

encoding → Encoding

  • 消息正文的編碼
  • read-only, inherited

hashCode → int

headers → Map<String, String>

  • HTTP标頭
  • final, inherited

isEmpty → bool

  • 如果為true,則read傳回的流将不會發出任何位元組
  • read-only, inherited

mimeType → String

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

runtimeType → Type

方法

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

read() → Stream<List<int>>

  • 傳回表示正文的Stream
  • inherited

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

  • 傳回包含Body作為String的Future
  • inherited

toString() → String

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見其他響應

屬性

expires → DateTime

  • 應将響應資料視為過時的日期和時間
  • read-only

lastModified → DateTime

  • 上次修改響應資料源的日期和時間
  • read-only

statusCode → int

  • 響應的HTTP狀态代碼
  • final

contentLength → int

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

context → Map<String, Object>

  • 中間件和處理程式可以使用的額外上下文
  • final, inherited

encoding → Encoding

  • 消息正文的編碼
  • read-only, inherited

hashCode → int

headers → Map<String, String>

  • HTTP标頭
  • final, inherited

isEmpty → bool

  • 如果為true,則read傳回的流将不會發出任何位元組
  • read-only, inherited

mimeType → String

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

runtimeType → Type

方法

change({Map<String, String> headers, Map<String, Object> 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請求流。

轉載于:https://my.oschina.net/u/3647851/blog/1841973