Skip to content

Commit

Permalink
Allow data in all request methods (cfug#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexV525 authored Dec 16, 2022
1 parent 873f572 commit 20a5920
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 56 deletions.
1 change: 1 addition & 0 deletions dio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 5.0.0-dev.1

- Allow `data` in all request methods.
- A platform independent `HttpClientAdapter` can now be instantiated by doing
`dio.httpClientAdapter = HttpClientAdapter();`.
- Add `ValidateCertificate` to handle certificate pinning better.
Expand Down
2 changes: 2 additions & 0 deletions dio/lib/src/dio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ abstract class Dio {
/// Handy method to make http GET request, which is a alias of [dio.fetch(RequestOptions)].
Future<Response<T>> get<T>(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
Expand All @@ -70,6 +71,7 @@ abstract class Dio {
/// Handy method to make http GET request, which is a alias of [dio.fetch(RequestOptions)].
Future<Response<T>> getUri<T>(
Uri uri, {
Object? data,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
Expand Down
22 changes: 15 additions & 7 deletions dio/lib/src/dio_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ abstract class DioMixin implements Dio {
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Object? data,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
}) {
return request<T>(
path,
data: data,
queryParameters: queryParameters,
options: checkOptions('GET', options),
onReceiveProgress: onReceiveProgress,
Expand All @@ -68,12 +70,14 @@ abstract class DioMixin implements Dio {
@override
Future<Response<T>> getUri<T>(
Uri uri, {
Object? data,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
}) {
return requestUri<T>(
uri,
data: data,
options: checkOptions('GET', options),
onReceiveProgress: onReceiveProgress,
cancelToken: cancelToken,
Expand Down Expand Up @@ -683,16 +687,19 @@ abstract class DioMixin implements Dio {
throw ArgumentError.value(options.method, "method");
}
final data = options.data;
List<int> bytes;
Stream<List<int>> stream;
const allowPayloadMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
if (data != null && allowPayloadMethods.contains(options.method)) {
if (data != null) {
final Stream<List<int>> stream;
// Handle the FormData
int? length;
if (data is Stream) {
assert(data is Stream<List>,
'Stream type must be `Stream<List>`, but ${data.runtimeType} is found.');
stream = data as Stream<List<int>>;
if (data is! Stream<List<int>>) {
throw ArgumentError.value(
data.runtimeType,
'data',
'Stream type must be `Stream<List<int>>`',
);
}
stream = data;
options.headers.keys.any((String key) {
if (key.toLowerCase() == Headers.contentLengthHeader) {
length = int.parse(options.headers[key].toString());
Expand All @@ -708,6 +715,7 @@ abstract class DioMixin implements Dio {
length = data.length;
options.headers[Headers.contentLengthHeader] = length.toString();
} else {
final List<int> bytes;
// Call request transformer.
final data = await transformer.transformRequest(options);
if (options.requestEncoder != null) {
Expand Down
28 changes: 4 additions & 24 deletions dio/lib/src/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ class BaseOptions extends _RequestConfig with OptionsMixin {
RequestEncoder? requestEncoder,
ResponseDecoder? responseDecoder,
ListFormat? listFormat,
this.setRequestContentTypeWhenNoPayload = false,
}) : assert(connectTimeout == null || !connectTimeout.isNegative),
assert(baseUrl.isEmpty || Uri.parse(baseUrl).host.isNotEmpty),
super(
Expand Down Expand Up @@ -145,7 +144,6 @@ class BaseOptions extends _RequestConfig with OptionsMixin {
RequestEncoder? requestEncoder,
ResponseDecoder? responseDecoder,
ListFormat? listFormat,
bool? setRequestContentTypeWhenNoPayload,
}) {
return BaseOptions(
method: method ?? this.method,
Expand All @@ -167,23 +165,8 @@ class BaseOptions extends _RequestConfig with OptionsMixin {
requestEncoder: requestEncoder ?? this.requestEncoder,
responseDecoder: responseDecoder ?? this.responseDecoder,
listFormat: listFormat ?? this.listFormat,
setRequestContentTypeWhenNoPayload: setRequestContentTypeWhenNoPayload ??
this.setRequestContentTypeWhenNoPayload,
);
}

static const _allowPayloadMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];

/// if false, content-type in request header will be deleted when method is not on of `_allowPayloadMethods`
bool setRequestContentTypeWhenNoPayload;

String? contentTypeWithRequestBody(String method) {
if (setRequestContentTypeWhenNoPayload) {
return contentType;
} else {
return _allowPayloadMethods.contains(method) ? contentType : null;
}
}
}

mixin OptionsMixin {
Expand Down Expand Up @@ -312,7 +295,7 @@ class Options {
if (this.headers != null) {
headers.addAll(this.headers!);
}
String? contentType = this.headers?[Headers.contentTypeHeader];
final String? contentType = this.headers?[Headers.contentTypeHeader];
final extra = Map<String, dynamic>.from(baseOpt.extra);
if (this.extra != null) {
extra.addAll(this.extra!);
Expand Down Expand Up @@ -341,14 +324,11 @@ class Options {
responseDecoder: responseDecoder ?? baseOpt.responseDecoder,
listFormat: listFormat ?? baseOpt.listFormat,
);

requestOptions.onReceiveProgress = onReceiveProgress;
requestOptions.onSendProgress = onSendProgress;
requestOptions.cancelToken = cancelToken;

requestOptions.contentType = contentType ??
this.contentType ??
baseOpt.contentTypeWithRequestBody(method);
requestOptions.contentType =
contentType ?? this.contentType ?? baseOpt.contentType;
return requestOptions;
}

Expand Down Expand Up @@ -661,7 +641,7 @@ class _RequestConfig {
};
this.responseType = responseType ?? ResponseType.json;
if (!contentTypeInHeader) {
this.contentType = contentType ?? Headers.jsonContentType;
this.contentType = contentType;
}
}

Expand Down
26 changes: 26 additions & 0 deletions dio/migration_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ When new content need to be added to the migration guide, make sure they're foll

### Summary

- `BaseOptions.setRequestContentTypeWhenNoPayload` has been removed.
- `get` and `getUri` in `Dio` has different signature.
- `DefaultHttpClientAdapter` is now named `IOHttpClientAdapter`,
and the platform independent adapter can be initiated by `HttpClientAdapter()` which is a factory method.
- Adapters that extends `HttpClientAdapter` must now `implements` instead of `extends`.
Expand All @@ -29,6 +31,30 @@ When new content need to be added to the migration guide, make sure they're foll

### Details

#### `get` and `getUri`

```diff
Future<Response<T>> get<T>(
String path, {
+ Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
});
```

```diff
Future<Response<T>> getUri<T>(
Uri uri, {
+ Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
});
```

#### `HttpClientAdapter`

Before:
Expand Down
63 changes: 39 additions & 24 deletions dio/test/options_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ void main() {

assert(bo1.headers['content-type'] == contentType);
assert(bo2.headers['content-type'] == contentType);
assert(bo3.headers['content-type'] == Headers.jsonContentType);
assert(bo3.headers['content-type'] == null);

try {
bo1.copyWith(headers: headers);
Expand Down Expand Up @@ -213,27 +213,19 @@ void main() {
dio.options.baseUrl = EchoAdapter.mockBase;
dio.httpClientAdapter = EchoAdapter();

Response r1 = await dio.get('');
assert(r1.requestOptions.headers[Headers.contentTypeHeader] == null);

dio.options.setRequestContentTypeWhenNoPayload = true;

r1 = await dio.get('');
assert(
r1.requestOptions.headers[Headers.contentTypeHeader] ==
Headers.jsonContentType,
final r1 = await dio.get('');
expect(
r1.requestOptions.headers[Headers.contentTypeHeader],
null,
);

dio.options.setRequestContentTypeWhenNoPayload = false;

final r2 = await dio.get(
'',
options: Options(contentType: Headers.jsonContentType),
);

assert(
r2.requestOptions.headers[Headers.contentTypeHeader] ==
Headers.jsonContentType,
expect(
r2.requestOptions.headers[Headers.contentTypeHeader],
Headers.jsonContentType,
);

final r3 = await dio.get(
Expand All @@ -242,21 +234,20 @@ void main() {
Headers.contentTypeHeader: Headers.jsonContentType,
}),
);
assert(
r3.requestOptions.headers[Headers.contentTypeHeader] ==
Headers.jsonContentType,
expect(
r3.requestOptions.headers[Headers.contentTypeHeader],
Headers.jsonContentType,
);

final r4 = await dio.post('', data: '');
assert(
r4.requestOptions.headers[Headers.contentTypeHeader] ==
Headers.jsonContentType,
expect(
r4.requestOptions.headers[Headers.contentTypeHeader],
null,
);
});

test('#test default content-type 2', () async {
final dio = Dio();
dio.options.setRequestContentTypeWhenNoPayload = true;
dio.options.baseUrl = 'https://www.example.com';

final r1 = Options(method: 'GET').compose(dio.options, '/test').copyWith(
Expand All @@ -280,7 +271,6 @@ void main() {
);
assert(false);
} catch (_) {}
dio.options.setRequestContentTypeWhenNoPayload = false;

final r3 = Options(method: 'GET').compose(dio.options, '/test');
assert(r3.uri.toString() == 'https://www.example.com/test');
Expand Down Expand Up @@ -349,4 +339,29 @@ void main() {
testInvalidArgumentException("CONNECT$separator");
}
});

test('Transform data correctly with requests', () async {
final dio = Dio()
..httpClientAdapter = EchoAdapter()
..options.baseUrl = EchoAdapter.mockBase;
const methods = [
'CONNECT',
'HEAD',
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS',
'TRACE',
];
for (final method in methods) {
final response = await dio.request(
'/test',
data: 'test',
options: Options(method: method),
);
expect(response.data, 'test');
}
});
}
1 change: 1 addition & 0 deletions dio/test/request_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ void main() {
dio = Dio();
dio.options
..baseUrl = serverUrl.toString()
..contentType = Headers.jsonContentType
..connectTimeout = Duration(seconds: 1)
..receiveTimeout = Duration(seconds: 5)
..headers = {'User-Agent': 'dartisan'};
Expand Down
2 changes: 1 addition & 1 deletion plugins/http2_adapter/lib/src/http2_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Http2Adapter implements HttpClientAdapter {
path = '/' + path;
}
if (uri.query.trim().isNotEmpty) {
path += ('?' + uri.query);
path += '?' + uri.query;
}
final headers = [
Header.ascii(':method', options.method),
Expand Down

0 comments on commit 20a5920

Please sign in to comment.