[Dart2] Fix a bug when asking to upload a MultipartFile as body. (#7736)

* Fix a bug when asking to upload a MultipartFile as body.

* Make finalizing MultipartFileRequest simpler.

* Restrict creating a MultipartFileRequest when not part of a multipart-form.

* Simplified the upload to use a StreamedRequest.

* Wrap all requests with try-catch.
This commit is contained in:
Noor Dawod 2020-11-04 02:35:42 +01:00 committed by GitHub
parent 2f30960349
commit aff1af7be5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 78 deletions

View File

@ -1,12 +1,5 @@
{{>header}} {{>header}}
{{>part_of}} {{>part_of}}
class QueryParam {
QueryParam(this.name, this.value);
String name;
String value;
}
class ApiClient { class ApiClient {
ApiClient({this.basePath = '{{{basePath}}}'}) { ApiClient({this.basePath = '{{{basePath}}}'}) {
{{#hasAuthMethods}} {{#hasAuthMethods}}
@ -88,11 +81,13 @@ class ApiClient {
headerParams.addAll(_defaultHeaderMap); headerParams.addAll(_defaultHeaderMap);
final ps = queryParams final urlEncodedQueryParams = queryParams
.where((p) => p.value != null) .where((param) => param.value != null)
.map((p) => '${p.name}=${Uri.encodeQueryComponent(p.value)}'); .map((param) => '$param');
final queryString = ps.isNotEmpty ? '?' + ps.join('&') : ''; final queryString = urlEncodedQueryParams.isNotEmpty
? '?${urlEncodedQueryParams.join('&')}'
: '';
final url = '$basePath$path$queryString'; final url = '$basePath$path$queryString';
@ -100,41 +95,61 @@ class ApiClient {
headerParams['Content-Type'] = nullableContentType; headerParams['Content-Type'] = nullableContentType;
} }
if (body is MultipartRequest) {
final request = MultipartRequest(method, Uri.parse(url));
request.fields.addAll(body.fields);
request.files.addAll(body.files);
request.headers.addAll(body.headers);
request.headers.addAll(headerParams);
final response = await _client.send(request);
return Response.fromStream(response);
}
final msgBody = nullableContentType == 'application/x-www-form-urlencoded'
? formParams
: serialize(body);
final nullableHeaderParams = headerParams.isEmpty ? null : headerParams;
try { try {
// Special case for uploading a single file which isnt a 'multipart/form-data'.
if (
body is MultipartFile && (nullableContentType == null ||
!nullableContentType.toLowerCase().startsWith('multipart/form-data'))
) {
final request = StreamedRequest(method, Uri.parse(url));
request.headers.addAll(headerParams);
request.contentLength = body.length;
body.finalize().listen(
request.sink.add,
onDone: request.sink.close,
onError: (error, trace) => request.sink.close(),
cancelOnError: true,
);
final response = await _client.send(request);
return Response.fromStream(response);
}
if (body is MultipartRequest) {
final request = MultipartRequest(method, Uri.parse(url));
request.fields.addAll(body.fields);
request.files.addAll(body.files);
request.headers.addAll(body.headers);
request.headers.addAll(headerParams);
final response = await _client.send(request);
return Response.fromStream(response);
}
final msgBody = nullableContentType == 'application/x-www-form-urlencoded'
? formParams
: serialize(body);
final nullableHeaderParams = headerParams.isEmpty ? null : headerParams;
switch(method) { switch(method) {
case 'POST': return await _client.post(url, headers: nullableHeaderParams, body: msgBody); case 'POST': return await _client.post(url, headers: nullableHeaderParams, body: msgBody,);
case 'PUT': return await _client.put(url, headers: nullableHeaderParams, body: msgBody); case 'PUT': return await _client.put(url, headers: nullableHeaderParams, body: msgBody,);
case 'DELETE': return await _client.delete(url, headers: nullableHeaderParams); case 'DELETE': return await _client.delete(url, headers: nullableHeaderParams,);
case 'PATCH': return await _client.patch(url, headers: nullableHeaderParams, body: msgBody); case 'PATCH': return await _client.patch(url, headers: nullableHeaderParams, body: msgBody,);
case 'HEAD': return await _client.head(url, headers: nullableHeaderParams); case 'HEAD': return await _client.head(url, headers: nullableHeaderParams,);
case 'GET': return await _client.get(url, headers: nullableHeaderParams); case 'GET': return await _client.get(url, headers: nullableHeaderParams,);
} }
} on SocketException catch (e, trace) { } on SocketException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'Socket operation failed: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'Socket operation failed: $method $path', e, trace,);
} on TlsException catch (e, trace) { } on TlsException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'TLS/SSL communication failed: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'TLS/SSL communication failed: $method $path', e, trace,);
} on IOException catch (e, trace) { } on IOException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'I/O operation failed: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'I/O operation failed: $method $path', e, trace,);
} on ClientException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'HTTP connection failed: $method $path', e, trace,);
} on Exception catch (e, trace) { } on Exception catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'Exception occurred: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'Exception occurred: $method $path', e, trace,);
} }
throw ApiException(HttpStatus.badRequest, 'Invalid HTTP operation: $method $path'); throw ApiException(HttpStatus.badRequest, 'Invalid HTTP operation: $method $path',);
} }
dynamic _deserialize(dynamic value, String targetType, {bool growable}) { dynamic _deserialize(dynamic value, String targetType, {bool growable}) {
@ -182,9 +197,9 @@ class ApiClient {
break; break;
} }
} on Exception catch (e, stack) { } on Exception catch (e, stack) {
throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', e, stack); throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', e, stack,);
} }
throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization'); throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization',);
} }
/// Update query and header parameters based on authentication settings. /// Update query and header parameters based on authentication settings.

View File

@ -1,5 +1,15 @@
{{>header}} {{>header}}
{{>part_of}} {{>part_of}}
class QueryParam {
const QueryParam(this.name, this.value);
final String name;
final String value;
@override
String toString() => '${Uri.encodeQueryComponent(name)}=${Uri.encodeQueryComponent(value)}';
}
// Ported from the Java version. // Ported from the Java version.
Iterable<QueryParam> _convertParametersForCollectionFormat( Iterable<QueryParam> _convertParametersForCollectionFormat(
String collectionFormat, String collectionFormat,

View File

@ -9,13 +9,6 @@
part of openapi.api; part of openapi.api;
class QueryParam {
QueryParam(this.name, this.value);
String name;
String value;
}
class ApiClient { class ApiClient {
ApiClient({this.basePath = 'http://petstore.swagger.io/v2'}) { ApiClient({this.basePath = 'http://petstore.swagger.io/v2'}) {
// Setup authentications (key: authentication name, value: authentication). // Setup authentications (key: authentication name, value: authentication).
@ -81,11 +74,13 @@ class ApiClient {
headerParams.addAll(_defaultHeaderMap); headerParams.addAll(_defaultHeaderMap);
final ps = queryParams final urlEncodedQueryParams = queryParams
.where((p) => p.value != null) .where((param) => param.value != null)
.map((p) => '${p.name}=${Uri.encodeQueryComponent(p.value)}'); .map((param) => '$param');
final queryString = ps.isNotEmpty ? '?' + ps.join('&') : ''; final queryString = urlEncodedQueryParams.isNotEmpty
? '?${urlEncodedQueryParams.join('&')}'
: '';
final url = '$basePath$path$queryString'; final url = '$basePath$path$queryString';
@ -93,41 +88,61 @@ class ApiClient {
headerParams['Content-Type'] = nullableContentType; headerParams['Content-Type'] = nullableContentType;
} }
if (body is MultipartRequest) {
final request = MultipartRequest(method, Uri.parse(url));
request.fields.addAll(body.fields);
request.files.addAll(body.files);
request.headers.addAll(body.headers);
request.headers.addAll(headerParams);
final response = await _client.send(request);
return Response.fromStream(response);
}
final msgBody = nullableContentType == 'application/x-www-form-urlencoded'
? formParams
: serialize(body);
final nullableHeaderParams = headerParams.isEmpty ? null : headerParams;
try { try {
// Special case for uploading a single file which isnt a 'multipart/form-data'.
if (
body is MultipartFile && (nullableContentType == null ||
!nullableContentType.toLowerCase().startsWith('multipart/form-data'))
) {
final request = StreamedRequest(method, Uri.parse(url));
request.headers.addAll(headerParams);
request.contentLength = body.length;
body.finalize().listen(
request.sink.add,
onDone: request.sink.close,
onError: (error, trace) => request.sink.close(),
cancelOnError: true,
);
final response = await _client.send(request);
return Response.fromStream(response);
}
if (body is MultipartRequest) {
final request = MultipartRequest(method, Uri.parse(url));
request.fields.addAll(body.fields);
request.files.addAll(body.files);
request.headers.addAll(body.headers);
request.headers.addAll(headerParams);
final response = await _client.send(request);
return Response.fromStream(response);
}
final msgBody = nullableContentType == 'application/x-www-form-urlencoded'
? formParams
: serialize(body);
final nullableHeaderParams = headerParams.isEmpty ? null : headerParams;
switch(method) { switch(method) {
case 'POST': return await _client.post(url, headers: nullableHeaderParams, body: msgBody); case 'POST': return await _client.post(url, headers: nullableHeaderParams, body: msgBody,);
case 'PUT': return await _client.put(url, headers: nullableHeaderParams, body: msgBody); case 'PUT': return await _client.put(url, headers: nullableHeaderParams, body: msgBody,);
case 'DELETE': return await _client.delete(url, headers: nullableHeaderParams); case 'DELETE': return await _client.delete(url, headers: nullableHeaderParams,);
case 'PATCH': return await _client.patch(url, headers: nullableHeaderParams, body: msgBody); case 'PATCH': return await _client.patch(url, headers: nullableHeaderParams, body: msgBody,);
case 'HEAD': return await _client.head(url, headers: nullableHeaderParams); case 'HEAD': return await _client.head(url, headers: nullableHeaderParams,);
case 'GET': return await _client.get(url, headers: nullableHeaderParams); case 'GET': return await _client.get(url, headers: nullableHeaderParams,);
} }
} on SocketException catch (e, trace) { } on SocketException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'Socket operation failed: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'Socket operation failed: $method $path', e, trace,);
} on TlsException catch (e, trace) { } on TlsException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'TLS/SSL communication failed: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'TLS/SSL communication failed: $method $path', e, trace,);
} on IOException catch (e, trace) { } on IOException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'I/O operation failed: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'I/O operation failed: $method $path', e, trace,);
} on ClientException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'HTTP connection failed: $method $path', e, trace,);
} on Exception catch (e, trace) { } on Exception catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'Exception occurred: $method $path', e, trace); throw ApiException.withInner(HttpStatus.badRequest, 'Exception occurred: $method $path', e, trace,);
} }
throw ApiException(HttpStatus.badRequest, 'Invalid HTTP operation: $method $path'); throw ApiException(HttpStatus.badRequest, 'Invalid HTTP operation: $method $path',);
} }
dynamic _deserialize(dynamic value, String targetType, {bool growable}) { dynamic _deserialize(dynamic value, String targetType, {bool growable}) {
@ -176,9 +191,9 @@ class ApiClient {
break; break;
} }
} on Exception catch (e, stack) { } on Exception catch (e, stack) {
throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', e, stack); throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', e, stack,);
} }
throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization'); throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization',);
} }
/// Update query and header parameters based on authentication settings. /// Update query and header parameters based on authentication settings.

View File

@ -9,6 +9,16 @@
part of openapi.api; part of openapi.api;
class QueryParam {
const QueryParam(this.name, this.value);
final String name;
final String value;
@override
String toString() => '${Uri.encodeQueryComponent(name)}=${Uri.encodeQueryComponent(value)}';
}
// Ported from the Java version. // Ported from the Java version.
Iterable<QueryParam> _convertParametersForCollectionFormat( Iterable<QueryParam> _convertParametersForCollectionFormat(
String collectionFormat, String collectionFormat,