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方法選擇要調用的處理程序,而級聯中間件可能會按順序調用每一個處理程序,直到返回成功的響應。瀏覽器
在處理程序之間路由請求的中間件應確保更新每一個請求的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處理程序。服務器
適配器必須處理來自處理程序的全部錯誤,包括返回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
實現適配器時,必須遵循一些規則。適配器不能將url或handlerPath參數傳遞給新的shelf.Request; 它應該只傳遞requestedUri。若是它傳遞了context參數,則全部Key必須以適配器的包名稱開頭,後跟句點。若是收到多個具備相同名稱的標頭,則適配器必須按照RFC 2616第4.2節將它們摺疊爲用逗號分隔的單個標頭。
若是基礎請求使用分塊傳輸編碼,則適配器必須先解碼主體,而後再將其傳遞給新的shelf.Request,並應刪除Transfer-Encoding標頭。這能夠確保當且僅當標頭聲明它們是時,纔會對郵件正文進行分塊。
適配器不得爲響應添加或修改任何實體標頭。
若是如下條件均不爲真,則適配器必須將分塊傳輸編碼應用於響應的正文並將其Transfer-Encoding標頭設置爲chunked:
若是底層服務器沒有手動實現,那麼適配器可能會發現[addChunkedEncoding()] [addChunkedEncoding]中間件對實現此行爲頗有用。
響應HEAD請求時,適配器不得發出實體主體。 不然,它不該以任何方式修改實體主體。
默認狀況下,適配器應在響應的Server標頭中包含有關其自身的信息。 若是處理程序返回帶有Server標頭集的響應,則該響應必須優先於適配器的默認標頭。
適配器應包含Date標頭以及處理程序返回響應的時間。 若是處理程序返回帶有Date標頭集的響應,則必須優先。
一個幫助程序,它按順序調用多個處理程序並返回第一個可接受的響應。[...]
默認狀況下,若是響應的狀態不是404或405,則認爲該響應是可接受的; 其餘狀態代表處理程序理解請求。
若是全部處理程序都返回不可接受的響應,則將返回最終響應。
var handler = new Cascade() .add(webSocketHandler) .add(staticFileHandler) .add(application) .handler;
構造函數
Cascade({Iterable<int> statusCodes, bool shouldCascade(Response response) })
建立一個新的空的cascase
屬性
方法
add(Handler handler) → Cascade
noSuchMethod(Invocation invocation) → dynamic
幫助程序,能夠輕鬆組成一組中間件和一個處理程序。
var handler = const Pipeline() .addMiddleware(loggingMiddleware) .addMiddleware(cachingMiddleware) .addHandler(application);
構造函數
Pipeline()
屬性
方法
addHandler(Handler handler) → Handler
addMiddleware(Middleware middleware) → Pipeline
noSuchMethod(Invocation invocation) → dynamic
表示要由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
屬性
contentLength → int
encoding → Encoding
isEmpty → bool
mimeType → String
方法
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
readAsString([Encoding encoding ]) → Future<String>
Response
處理程序返回的響應
構造函數
Response(int statusCode, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
Response.forbidden(dynamic body, { Map<String, String> headers, Encoding encoding, Map<String, Object> context })
Response.found(dynamic location, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
Response.internalServerError({dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
Response.movedPermanently(dynamic location, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
Response.notFound(dynamic body, { Map<String, String> headers, Encoding encoding, Map<String, Object> context })
Response.notModified({Map<String, String> headers, Map<String, Object> context })
Response.ok(dynamic body, { Map<String, String> headers, Encoding encoding, Map<String, Object> context })
Response.seeOther(dynamic location, { dynamic body, Map<String, String> headers, Encoding encoding, Map<String, Object> context })
屬性
contentLength → int
encoding → Encoding
isEmpty → bool
mimeType → String
方法
change({Map<String, String> headers, Map<String, Object> context, dynamic body }) → Response
noSuchMethod(Invocation invocation) → dynamic
readAsString([Encoding encoding ]) → Future<String>
Server
具備具體URL的適配器
「適配器」的最基本定義包括將傳入請求傳遞給處理程序並將其響應傳遞給某個外部客戶端的任何函數,可是,在實踐中,大多數適配器也是服務器 - 也就是說,它們正在處理對某個已知URL進行的請求
此接口以通常方式表示這些服務器。 它對於編寫須要知道本身的URL而不將該代碼緊密耦合到單個服務器實現的代碼頗有用
這個接口有兩個內置的實現。 您可使用IOServer建立由dart:io支持的服務器,或者您可使用ServerHandler建立由普通Handler支持的服務器
此接口的實現負責確保成員按照文檔的方式工做
Implemented by IOServer
構造函數
Server()
屬性
方法
noSuchMethod(Invocation invocation) → dynamic
鏈接的服務器和處理程序對
處理程序的請求一旦可用就會發送到服務器的掛載處理程序。這用於公開其實是較大URL空間的一部分的虛擬服務器。
構造函數
ServerHandler(Uri url, { dynamic onClose() })
屬性
方法
noSuchMethod(Invocation invocation) → dynamic
中間件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())); })
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); }); }; }; }
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; }); }; };
FutureOr<Response> Handler ( Request request )
處理請求的函數
例如,靜態文件處理程序能夠從文件系統讀取請求的URI,並將其做爲Response的主體返回
包裝一個或多個其餘處理程序以執行前處理或後處理的處理程序稱爲「中間件」
處理程序能夠直接從HTTP服務器接收請求,或者可能已被其餘中間件處理過。相似地,響應能夠由HTTP服務器直接返回,或者由其餘中間件完成進一步處理
Handler Middleware ( Handler innerHandler )
經過包裝處理程序建立新Handler的函數
您能夠經過將處理程序包裝在中間件中來擴展其功能,中間件能夠在請求發送處處理程序以前攔截並處理請求,處理程序發送後的響應或者二者均可以。
因爲中間件使用處理程序並返回新的處理程序,所以能夠將多箇中間件實例組合在一塊兒以提供豐富的功能。
中間件的常見用途包括緩存,日誌記錄和身份驗證。
捕獲異常的中間件應確保無需修改便可傳遞HijackExceptions。
可使用createMiddleware建立一個簡單的中間件
用於表示請求已被劫持的異常
除了建立可劫持請求的Shelf適配器以外的任何代碼都不該捕獲此內容。 捕獲異常的中間件應確保傳遞HijackExceptions
另請參見Request.hijack。
Implements Exception
構造函數
const
屬性
方法
noSuchMethod(Invocation invocation) → dynamic
由dart:io HttpServer支持的服務器
Implements Server
構造函數
IOServer(HttpServer server)
屬性
方法
noSuchMethod(Invocation invocation) → dynamic
靜態方法
bind(dynamic address, int port, { int backlog }) → Future<IOServer>
handleRequest(HttpRequest request, Handler handler) → Future
serve(Handler handler, dynamic address, int port, { SecurityContext securityContext, int backlog }) → Future<HttpServer>
serveRequests(Stream<HttpRequest> requests, Handler handler) → void