Dart服務器端 shelf包

介紹

Shelf能夠輕鬆建立和組合Web服務器和Web服務器的一部分。 怎麼樣?html

  • 暴露一小部分簡單類型。
  • 將服務器邏輯映射爲一個簡單的函數:請求的單個參數,響應是返回值。
  • 簡單地混合和匹配同步和異步處理。
  • 靈活地返回具備相同模型的簡單字符串或字節流。

例子

參見example / example.dartjava

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的靜態文件服務器 - 或者它能夠進行一些處理並將其轉發到另外一個處理程序 - 例如,打印有關信息的記錄器 請求和對命令行的響應。web

後一種處理程序稱爲「中間件」,由於它位於服務器堆棧的中間。中間件能夠被認爲是一個函數,它接受一個處理程序並將其包裝在另外一個處理程序中以提供其餘功能。Shelf應用程序一般由多層中間件組成,中間有一個或多個處理程序; shelf.Pipeline類使這種應用程序易於構建。api

一些中間件也能夠採用多個處理程序,併爲每一個請求調用其中一個或多個。例如,路由中間件可能會根據請求的URI或HTTP方法選擇要調用的處理程序,而級聯中間件可能會按順序調用每一個處理程序,直到返回成功的響應。瀏覽器

在處理程序之間路由請求的中間件應確保更新每一個請求的handlerPathurl。 這容許內部處理程序知道它們在應用程序中的位置,以便它們能夠正確地執行本身的路由。 這可使用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.locationwindow.history在瀏覽器中合成HTTP請求,或者它可能直接將請求從HTTP客戶端傳遞到Shelf處理程序。服務器

API要求

適配器必須處理來自處理程序的全部錯誤,包括返回null響應的處理程序。若是可能的話,它應該將每一個錯誤打印到控制檯,而後就像處理程序返回500響應同樣。適配器可能包含500響應的正文數據,但此正文數據不得包含有關發生的錯誤的信息。這可確保默認狀況下意外錯誤不會致使生產中的內部信息泄露; 若是用戶想要返回詳細的錯誤描述,他們應該明確包含中間件來執行此操做。app

適配器應確保處理程序拋出的異步錯誤不會致使應用程序崩潰,即便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接口的實現。async

Request 要求

實現適配器時,必須遵循一些規則。適配器不能將urlhandlerPath參數傳遞給新的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將收到一個請求。 它能夠經過返回ResponseFuture<Response>來響應請求。對於部分requestHandler也能夠返回null,貨所有請求被髮送到內部處理程序

若是提供,則使用內部處理程序生成的響應調用responseHandlerrequestHandler生成的響應不會發送到responseHandler

responseHandler應該返回ResponseFuture <Response>。 它能夠返回它接收的響應參數或建立一個新的Response對象

若是提供,errorHandler會收到內部處理程序拋出的錯誤。 它不會收到requestHandlerresponseHandler拋出的錯誤,也不會收到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請求流。
相關文章
相關標籤/搜索