Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(337)

Side by Side Diff: pkg/pubserver/lib/shelf_pubserver.dart

Issue 917793002: Use pub_server package (Closed) Base URL: git@github.com:dart-lang/pub-dartlang-dart.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « pkg/pubserver/lib/repository.dart ('k') | pkg/pubserver/pubspec.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
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.
4
5 library pubserver.shelf_pubserver;
6
7 import 'dart:async';
8 import 'dart:convert';
9
10 import 'package:logging/logging.dart';
11 import 'package:mime/mime.dart';
12 import 'package:pub_semver/pub_semver.dart';
13 import 'package:shelf/shelf.dart' as shelf;
14 import 'package:yaml/yaml.dart';
15
16 import 'repository.dart';
17
18 final Logger _logger = new Logger('pubserver.shelf_pubserver');
19
20 // TODO: Error handling from [PackageRepo] class.
21 // Distinguish between:
22 // - Unauthorized Error
23 // - Version Already Exists Error
24 // - Internal Server Error
25 /// A shelf handler for serving a pub [PackageRepository].
26 ///
27 /// The following API endpoints are provided by this shelf handler:
28 ///
29 /// * Getting information about all versions of a package.
30 ///
31 /// GET /api/packages/<package-name>
32 /// [200 OK] [Content-Type: application/json]
33 /// {
34 /// "name" : "<package-name>",
35 /// "latest" : { ...},
36 /// "versions" : [
37 /// {
38 /// "version" : "<version>",
39 /// "archive_url" : "<download-url tar.gz>",
40 /// "pubspec" : {
41 /// "author" : ...,
42 /// "dependencies" : { ... },
43 /// ...
44 /// },
45 /// },
46 /// ...
47 /// ],
48 /// }
49 /// or
50 /// [404 Not Found]
51 ///
52 /// * Getting information about a specific (package, version) pair.
53 ///
54 /// GET /api/packages/<package-name>/versions/<version-name>
55 /// [200 OK] [Content-Type: application/json]
56 /// {
57 /// "version" : "<version>",
58 /// "archive_url" : "<download-url tar.gz>",
59 /// "pubspec" : {
60 /// "author" : ...,
61 /// "dependencies" : { ... },
62 /// ...
63 /// },
64 /// }
65 /// or
66 /// [404 Not Found]
67 ///
68 /// * Downloading package.
69 ///
70 /// GET /api/packages/<package-name>/versions/<version-name>.tar.gz
71 /// [200 OK] [Content-Type: octet-stream ??? FIXME ???]
72 /// or
73 /// [302 Found / Temporary Redirect]
74 /// Location: <new-location>
75 /// or
76 /// [404 Not Found]
77 ///
78 /// * Uploading
79 ///
80 /// GET /api/packages/versions/new
81 /// Headers:
82 /// Authorization: Bearer <oauth2-token>
83 /// [200 OK]
84 /// {
85 /// "fields" : {
86 /// "a": "...",
87 /// "b": "...",
88 /// ...
89 /// },
90 /// "url" : "https://storage.googleapis.com"
91 /// }
92 ///
93 /// POST "https://storage.googleapis.com"
94 /// Headers:
95 /// a: ...
96 /// b: ...
97 /// ...
98 /// <multipart> file package.tar.gz
99 /// [302 Found / Temporary Redirect]
100 /// Location: https://pub.dartlang.org/finishUploadUrl
101 ///
102 /// GET https://pub.dartlang.org/finishUploadUrl
103 /// [200 OK]
104 /// {
105 /// "success" : {
106 /// "message": "Successfully uploaded package.",
107 /// },
108 /// }
109 ///
110 /// It will use the pub [PackageRepository] given in the constructor to provide
111 /// this HTTP endpoint.
112 class ShelfPubServer {
113 static final RegExp _packageRegexp =
114 new RegExp(r'^/api/packages/([^/]+)$');
115
116 static final RegExp _versionRegexp =
117 new RegExp(r'^/api/packages/([^/]+)/versions/([^/]+)$');
118
119 static final RegExp _downloadRegexp =
120 new RegExp(r'^/packages/([^/]+)/versions/([^/]+)\.tar\.gz$');
121
122 static final RegExp _boundaryRegExp = new RegExp(r'^.*boundary="([^"]+)"$');
123
124
125 final PackageRepository repository;
126
127 ShelfPubServer(this.repository);
128
129
130 Future<shelf.Response> requestHandler(shelf.Request request) {
131 String path = request.requestedUri.path;
132 if (request.method == 'GET') {
133 var downloadMatch = _downloadRegexp.matchAsPrefix(path);
134 if (downloadMatch != null) {
135 var package = Uri.decodeComponent(downloadMatch.group(1));
136 var version = Uri.decodeComponent(downloadMatch.group(2));
137 return _download(request.requestedUri, package, version);
138 }
139
140 var packageMatch = _packageRegexp.matchAsPrefix(path);
141 if (packageMatch != null) {
142 var package = Uri.decodeComponent(packageMatch.group(1));
143 return _listVersions(request.requestedUri, package);
144 }
145
146 var versionMatch = _versionRegexp.matchAsPrefix(path);
147 if (versionMatch != null) {
148 var package = Uri.decodeComponent(versionMatch.group(1));
149 var version = Uri.decodeComponent(versionMatch.group(2));
150 return _showVersion(request.requestedUri, package, version);
151 }
152
153 if (path == '/api/packages/versions/new') {
154 if (!repository.supportsUpload) {
155 return new Future.value(new shelf.Response.notFound(null));
156 }
157
158 if (repository.supportsAsyncUpload) {
159 return _startUploadAsync(request.requestedUri);
160 } else {
161 return _startUploadSimple(request.requestedUri);
162 }
163 }
164
165 if (path == '/api/packages/versions/newUploadFinish') {
166 if (!repository.supportsUpload) {
167 return new Future.value(new shelf.Response.notFound(null));
168 }
169
170 if (repository.supportsAsyncUpload) {
171 return _finishUploadAsync(request.requestedUri);
172 } else {
173 return _finishUploadSimple(request.requestedUri);
174 }
175 }
176 } else if (request.method == 'POST') {
177 if (!repository.supportsUpload) {
178 return new Future.value(new shelf.Response.notFound(null));
179 }
180
181 if (path == '/api/packages/versions/newUpload') {
182 return _uploadSimple(
183 request.requestedUri,
184 request.headers['content-type'],
185 request.read());
186 }
187 }
188 return new Future.value(new shelf.Response.notFound(null));
189 }
190
191
192 // Metadata handlers.
193
194 Future<shelf.Response> _listVersions(Uri uri, String package) {
195 return repository.versions(package).toList()
196 .then((List<PackageVersion> packageVersions) {
197 if (packageVersions.length == 0) {
198 return new shelf.Response.notFound(null);
199 }
200
201 packageVersions.sort((a, b) => a.version.compareTo(b.version));
202
203 // TODO: Add legacy entries (if necessary), such as version_url.
204 Map packageVersion2Json(PackageVersion version) {
205 return {
206 'archive_url':
207 '${_downloadUrl(
208 uri, version.packageName, version.versionString)}',
209 'pubspec': loadYaml(version.pubspecYaml),
210 'version': version.versionString,
211 };
212 }
213
214 var latestVersion = packageVersions.last;
215 for (int i = packageVersions.length - 1; i >= 0; i--) {
216 if (!packageVersions[i].version.isPreRelease) {
217 latestVersion = packageVersions[i];
218 break;
219 }
220 }
221
222 // TODO: The 'latest' is something we should get rid of, since it's
223 // duplicated in 'versions'.
224 return _jsonResponse({
225 'name' : package,
226 'latest' : packageVersion2Json(latestVersion),
227 'versions' : packageVersions.map(packageVersion2Json).toList(),
228 });
229 });
230 }
231
232 Future<shelf.Response> _showVersion(Uri uri, String package, String version) {
233 return repository
234 .lookupVersion(package, version).then((PackageVersion version) {
235 if (version == null) {
236 return new shelf.Response.notFound('');
237 }
238
239 // TODO: Add legacy entries (if necessary), such as version_url.
240 return _jsonResponse({
241 'archive_url':
242 '${_downloadUrl(
243 uri, version.packageName, version.versionString)}',
244 'pubspec': loadYaml(version.pubspecYaml),
245 'version': version.versionString,
246 });
247 });
248 }
249
250
251 // Download handlers.
252
253 Future<shelf.Response> _download(Uri uri, String package, String version) {
254 if (repository.supportsDownloadUrl) {
255 return repository.downloadUrl(package, version).then((Uri url) {
256 // This is a redirect to [url]
257 return new shelf.Response.seeOther(url);
258 });
259 }
260 return repository.download(package, version).then((stream) {
261 return new shelf.Response.ok(stream);
262 });
263 }
264
265
266 // Upload async handlers.
267
268 Future<shelf.Response> _startUploadAsync(Uri uri) {
269 return repository.startAsyncUpload(_finishUploadAsyncUrl(uri))
270 .then((AsyncUploadInfo info) {
271 return _jsonResponse({
272 'url' : '${info.uri}',
273 'fields' : info.fields,
274 });
275 });
276 }
277
278 Future<shelf.Response> _finishUploadAsync(Uri uri) {
279 return repository.finishAsyncUpload(uri).then((_) {
280 return _jsonResponse({
281 'success' : {
282 'message' : 'Successfully uploaded package.',
283 },
284 });
285 }).catchError((error, stack) {
286 return _jsonResponse({
287 'error' : {
288 'message' : '$error.',
289 },
290 }, status: 400);
291 });
292 }
293
294
295 // Upload custom handlers.
296
297 Future<shelf.Response> _startUploadSimple(Uri url) {
298 _logger.info('Start simple upload.');
299 return _jsonResponse({
300 'url' : '${_uploadSimpleUrl(url)}',
301 'fields' : {},
302 });
303 }
304
305 Future<shelf.Response> _uploadSimple(
306 Uri uri, String contentType, Stream<List<int>> stream) {
307 _logger.info('Perform simple upload.');
308 if (contentType.startsWith('multipart/form-data')) {
309 var match = _boundaryRegExp.matchAsPrefix(contentType);
310 if (match != null) {
311 var boundary = match.group(1);
312 return stream
313 .transform(new MimeMultipartTransformer(boundary))
314 .first.then((MimeMultipart part) {
315 // TODO: Ensure that `part.headers['content-disposition']` is
316 // `form-data; name="file"; filename="package.tar.gz`
317 return repository.upload(part).then((_) {
318 return new shelf.Response.found(_finishUploadSimpleUrl(uri));
319 }).catchError((error, stack) {
320 // TODO: Do error checking and return error codes?
321 return new shelf.Response.found(
322 _finishUploadSimpleUrl(uri, error: error));
323 });
324 });
325 }
326 }
327 return
328 _badRequest('Upload must contain a multipart/form-data content type.');
329 }
330
331 Future<shelf.Response> _finishUploadSimple(Uri uri) {
332 var error = uri.queryParameters['error'];
333 _logger.info('Finish simple upload (error: $error).');
334 if (error != null) {
335 return _jsonResponse(
336 { 'error' : { 'message' : error } }, status: 400);
337 }
338 return _jsonResponse(
339 { 'success' : { 'message' : 'Successfully uploaded package.' } });
340 }
341
342
343 // Helper functions.
344
345 Future<shelf.Response> _badRequest(String message) {
346 return new Future.value(new shelf.Response(
347 400,
348 body: JSON.encode({ 'error' : message }),
349 headers: {'content-type': 'application/json'}));
350 }
351
352 Future<shelf.Response> _jsonResponse(Map json, {int status: 200}) {
353 return new Future.sync(() {
354 return new shelf.Response(status,
355 body: JSON.encode(json),
356 headers: {'content-type': 'application/json'});
357 });
358 }
359
360
361 // Metadata urls.
362
363 Uri _packageUrl(Uri url, String package) {
364 var encode = Uri.encodeComponent;
365 return url.resolve('/api/packages/${encode(package)}');
366 }
367
368
369 // Download urls.
370
371 Uri _downloadUrl(Uri url, String package, String version) {
372 var encode = Uri.encodeComponent;
373 return url.resolve(
374 '/packages/${encode(package)}/versions/${encode(version)}.tar.gz');
375 }
376
377
378 // Upload async urls.
379
380 Uri _startUploadAsyncUrl(Uri url) {
381 var encode = Uri.encodeComponent;
382 return url.resolve('/api/packages/versions/new');
383 }
384
385 Uri _finishUploadAsyncUrl(Uri url) {
386 var encode = Uri.encodeComponent;
387 return url.resolve('/api/packages/versions/newUploadFinish');
388 }
389
390
391 // Upload custom urls.
392
393 Uri _uploadSimpleUrl(Uri url) {
394 return url.resolve('/api/packages/versions/newUpload');
395 }
396
397 Uri _finishUploadSimpleUrl(Uri url, {String error}) {
398 var postfix = error == null ? '' : '?error=${Uri.encodeComponent(error)}';
399 return url.resolve('/api/packages/versions/newUploadFinish$postfix');
400 }
401 }
OLDNEW
« no previous file with comments | « pkg/pubserver/lib/repository.dart ('k') | pkg/pubserver/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698