OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
| 6 import 'dart:convert'; |
6 import 'dart:io'; | 7 import 'dart:io'; |
7 | 8 |
8 import 'package:barback/barback.dart'; | 9 import 'package:barback/barback.dart'; |
| 10 import "package:crypto/crypto.dart"; |
9 import 'package:mime/mime.dart'; | 11 import 'package:mime/mime.dart'; |
10 import 'package:path/path.dart' as path; | 12 import 'package:path/path.dart' as path; |
11 import 'package:shelf/shelf.dart' as shelf; | 13 import 'package:shelf/shelf.dart' as shelf; |
12 import 'package:stack_trace/stack_trace.dart'; | 14 import 'package:stack_trace/stack_trace.dart'; |
13 | 15 |
14 import '../barback.dart'; | 16 import '../barback.dart'; |
15 import '../io.dart'; | 17 import '../io.dart'; |
16 import '../log.dart' as log; | 18 import '../log.dart' as log; |
17 import '../utils.dart'; | 19 import '../utils.dart'; |
18 import 'base_server.dart'; | 20 import 'base_server.dart'; |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
82 var parts = path.url.split(url.path); | 84 var parts = path.url.split(url.path); |
83 | 85 |
84 // Strip the leading "/" from the URL. | 86 // Strip the leading "/" from the URL. |
85 if (parts.isNotEmpty && parts.first == "/") parts = parts.skip(1); | 87 if (parts.isNotEmpty && parts.first == "/") parts = parts.skip(1); |
86 | 88 |
87 var relativePath = path.url.join(rootDirectory, path.url.joinAll(parts)); | 89 var relativePath = path.url.join(rootDirectory, path.url.joinAll(parts)); |
88 return new AssetId(package, relativePath); | 90 return new AssetId(package, relativePath); |
89 } | 91 } |
90 | 92 |
91 /// Handles an HTTP request. | 93 /// Handles an HTTP request. |
92 handleRequest(shelf.Request request) { | 94 Future<shelf.Response> handleRequest(shelf.Request request) async { |
93 if (request.method != "GET" && request.method != "HEAD") { | 95 if (request.method != "GET" && request.method != "HEAD") { |
94 return methodNotAllowed(request); | 96 return methodNotAllowed(request); |
95 } | 97 } |
96 | 98 |
97 var id; | 99 var id; |
98 try { | 100 try { |
99 id = urlToId(request.url); | 101 id = urlToId(request.url); |
100 } on FormatException catch (ex) { | 102 } on FormatException catch (ex) { |
101 // If we got here, we had a path like "/packages" which is a special | 103 // If we got here, we had a path like "/packages" which is a special |
102 // directory, but not a valid path since it lacks a following package | 104 // directory, but not a valid path since it lacks a following package |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
147 // Allow requests of any origin to access "pub serve". This is useful for | 149 // Allow requests of any origin to access "pub serve". This is useful for |
148 // running "pub serve" in parallel with another development server. Since | 150 // running "pub serve" in parallel with another development server. Since |
149 // "pub serve" is only used as a development server and doesn't require | 151 // "pub serve" is only used as a development server and doesn't require |
150 // any sort of credentials anyway, this is secure. | 152 // any sort of credentials anyway, this is secure. |
151 return response.change( | 153 return response.change( |
152 headers: const {"Access-Control-Allow-Origin": "*"}); | 154 headers: const {"Access-Control-Allow-Origin": "*"}); |
153 }); | 155 }); |
154 } | 156 } |
155 | 157 |
156 /// Returns the body of [asset] as a response to [request]. | 158 /// Returns the body of [asset] as a response to [request]. |
157 Future<shelf.Response> _serveAsset(shelf.Request request, Asset asset) { | 159 Future<shelf.Response> _serveAsset(shelf.Request request, Asset asset) async { |
158 return validateStream(asset.read()).then((stream) { | 160 try { |
159 addResult(new BarbackServerResult._success(request.url, asset.id)); | 161 var pair = tee(await validateStream(asset.read())); |
160 var headers = {}; | 162 var responseStream = pair.first; |
161 var mimeType = lookupMimeType(asset.id.path); | 163 var hashStream = pair.last; |
162 if (mimeType != null) headers['Content-Type'] = mimeType; | 164 |
163 return new shelf.Response.ok(stream, headers: headers); | 165 // Allow the asset to be cached based on its content hash. |
164 }).catchError((error, trace) { | 166 var sha = new SHA1(); |
| 167 await hashStream.forEach((chunk) { |
| 168 sha.add(chunk); |
| 169 }); |
| 170 |
| 171 var assetSha = BASE64.encode(sha.close()); |
| 172 var previousSha = request.headers["if-none-match"]; |
| 173 |
| 174 var headers = { |
| 175 // Enabled browser caching of the asset. |
| 176 "Cache-Control": "max-age=3600", |
| 177 "ETag": assetSha |
| 178 }; |
| 179 |
| 180 if (assetSha == previousSha) { |
| 181 // We're requesting an unchanged asset so don't push its body down the |
| 182 // wire again. |
| 183 addResult(new BarbackServerResult._cached(request.url, asset.id)); |
| 184 return new shelf.Response.notModified(headers: headers); |
| 185 } else { |
| 186 addResult(new BarbackServerResult._success(request.url, asset.id)); |
| 187 var mimeType = lookupMimeType(asset.id.path); |
| 188 if (mimeType != null) headers['Content-Type'] = mimeType; |
| 189 return new shelf.Response.ok(responseStream, headers: headers); |
| 190 } |
| 191 } catch (error, trace) { |
165 addResult(new BarbackServerResult._failure(request.url, asset.id, error)); | 192 addResult(new BarbackServerResult._failure(request.url, asset.id, error)); |
166 | 193 |
167 // If we couldn't read the asset, handle the error gracefully. | 194 // If we couldn't read the asset, handle the error gracefully. |
168 if (error is FileSystemException) { | 195 if (error is FileSystemException) { |
169 // Assume this means the asset was a file-backed source asset | 196 // Assume this means the asset was a file-backed source asset |
170 // and we couldn't read it, so treat it like a missing asset. | 197 // and we couldn't read it, so treat it like a missing asset. |
171 return notFound(request, error: error.toString(), asset: asset.id); | 198 return notFound(request, error: error.toString(), asset: asset.id); |
172 } | 199 } |
173 | 200 |
174 trace = new Chain.forTrace(trace); | 201 trace = new Chain.forTrace(trace); |
175 logRequest(request, "$error\n$trace"); | 202 logRequest(request, "$error\n$trace"); |
176 | 203 |
177 // Otherwise, it's some internal error. | 204 // Otherwise, it's some internal error. |
178 return new shelf.Response.internalServerError(body: error.toString()); | 205 return new shelf.Response.internalServerError(body: error.toString()); |
179 }); | 206 } |
180 } | 207 } |
181 } | 208 } |
182 | 209 |
183 /// The result of the server handling a URL. | 210 /// The result of the server handling a URL. |
184 /// | 211 /// |
185 /// Only requests for which an asset was requested from barback will emit a | 212 /// Only requests for which an asset was requested from barback will emit a |
186 /// result. Malformed requests will be handled internally. | 213 /// result. Malformed requests will be handled internally. |
187 class BarbackServerResult { | 214 class BarbackServerResult { |
188 /// The requested url. | 215 /// The requested url. |
189 final Uri url; | 216 final Uri url; |
190 | 217 |
191 /// The id that [url] identifies. | 218 /// The id that [url] identifies. |
192 final AssetId id; | 219 final AssetId id; |
193 | 220 |
194 /// The error thrown by barback. | 221 /// The error thrown by barback. |
195 /// | 222 /// |
196 /// If the request was served successfully, this will be null. | 223 /// If the request was served successfully, this will be null. |
197 final error; | 224 final error; |
198 | 225 |
199 /// Whether the request was served successfully. | 226 /// Whether the request was served successfully. |
200 bool get isSuccess => error == null; | 227 bool get isSuccess => error == null; |
201 | 228 |
| 229 /// Whether the request was for a previously cached asset. |
| 230 final bool isCached; |
| 231 |
202 /// Whether the request was served unsuccessfully. | 232 /// Whether the request was served unsuccessfully. |
203 bool get isFailure => !isSuccess; | 233 bool get isFailure => !isSuccess; |
204 | 234 |
205 BarbackServerResult._success(this.url, this.id) | 235 BarbackServerResult._success(this.url, this.id) |
206 : error = null; | 236 : error = null, |
| 237 isCached = false; |
207 | 238 |
208 BarbackServerResult._failure(this.url, this.id, this.error); | 239 BarbackServerResult._cached(this.url, this.id) |
| 240 : error = null, |
| 241 isCached = true; |
| 242 |
| 243 BarbackServerResult._failure(this.url, this.id, this.error) |
| 244 : isCached = false; |
209 } | 245 } |
OLD | NEW |