Merge pull request #857 from synapsecode/feat/better_networking

better_networking Package Creation
This commit is contained in:
Ankit Mahato
2025-06-29 06:58:27 +05:30
committed by GitHub
54 changed files with 1876 additions and 671 deletions

View File

@ -3,13 +3,9 @@ library apidash_core;
export 'consts.dart';
export 'extensions/extensions.dart';
export 'models/models.dart';
export 'utils/utils.dart';
export 'services/services.dart';
export 'import_export/import_export.dart';
export 'utils/utils.dart';
// Export 3rd party packages
export 'package:collection/collection.dart';
export 'package:freezed_annotation/freezed_annotation.dart';
export 'package:http/http.dart';
export 'package:http_parser/http_parser.dart';
export 'package:seed/seed.dart';
export 'package:better_networking/better_networking.dart';

View File

@ -1,98 +1 @@
import 'dart:convert';
enum APIType {
rest("HTTP", "HTTP"),
graphql("GraphQL", "GQL");
const APIType(this.label, this.abbr);
final String label;
final String abbr;
}
enum EnvironmentVariableType { variable, secret }
enum HTTPVerb {
get("GET"),
head("HEAD"),
post("POST"),
put("PUT"),
patch("PAT"),
delete("DEL"),
options("OPT");
const HTTPVerb(this.abbr);
final String abbr;
}
enum SupportedUriSchemes { https, http }
final kSupportedUriSchemes =
SupportedUriSchemes.values.map((i) => i.name).toList();
const kDefaultUriScheme = SupportedUriSchemes.https;
final kLocalhostRegex = RegExp(r'^localhost(:\d+)?(/.*)?$');
final kIPHostRegex =
RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(:\d+)?(/.*)?$');
const kMethodsWithBody = [
HTTPVerb.post,
HTTPVerb.put,
HTTPVerb.patch,
HTTPVerb.delete,
];
const kDefaultHttpMethod = HTTPVerb.get;
const kDefaultContentType = ContentType.json;
const kTypeApplication = 'application';
// application
const kSubTypeJson = 'json';
const kSubTypeOctetStream = 'octet-stream';
const kSubTypePdf = 'pdf';
const kSubTypeSql = 'sql';
const kSubTypeXml = 'xml';
const kSubTypeYaml = 'yaml';
const kSubTypeXYaml = 'x-yaml';
const kSubTypeYml = 'x-yml';
const kSubTypeXWwwFormUrlencoded = 'x-www-form-urlencoded';
const kTypeText = 'text';
// text
const kSubTypeCss = 'css';
const kSubTypeCsv = 'csv';
const kSubTypeHtml = 'html';
const kSubTypeJavascript = 'javascript';
const kSubTypeMarkdown = 'markdown';
const kSubTypePlain = 'plain';
const kSubTypeTextXml = 'xml';
const kSubTypeTextYaml = 'yaml';
const kSubTypeTextYml = 'yml';
const kTypeImage = 'image';
//image
const kSubTypeSvg = 'svg+xml';
const kTypeAudio = 'audio';
const kTypeVideo = 'video';
const kTypeMultipart = "multipart";
const kSubTypeFormData = "form-data";
const kSubTypeDefaultViewOptions = 'all';
enum ContentType {
json("$kTypeApplication/$kSubTypeJson"),
text("$kTypeText/$kSubTypePlain"),
formdata("$kTypeMultipart/$kSubTypeFormData");
const ContentType(this.header);
final String header;
}
const JsonEncoder kJsonEncoder = JsonEncoder.withIndent(' ');
const JsonDecoder kJsonDecoder = JsonDecoder();
const LineSplitter kSplitter = LineSplitter();
const kCodeCharsPerLineLimit = 200;
const kHeaderContentType = "Content-Type";
const kMsgRequestCancelled = 'Request Cancelled';

View File

@ -1,2 +1 @@
export 'string_extensions.dart';
export 'map_extensions.dart';

View File

@ -1,7 +1,5 @@
import 'package:better_networking/better_networking.dart';
import 'package:curl_parser/curl_parser.dart';
import '../consts.dart';
import '../models/models.dart';
import '../utils/utils.dart';
class CurlIO {
List<HttpRequestModel>? getHttpRequestModelList(String content) {

View File

@ -1,8 +1,5 @@
import 'package:better_networking/better_networking.dart';
import 'package:har/har.dart' as har;
import 'package:seed/seed.dart';
import '../consts.dart';
import '../models/models.dart';
import '../utils/utils.dart';
class HarParserIO {
List<(String?, HttpRequestModel)>? getHttpRequestModelList(String content) {

View File

@ -1,9 +1,8 @@
import 'package:better_networking/better_networking.dart';
import 'package:flutter/material.dart';
import 'package:insomnia_collection/insomnia_collection.dart';
import 'package:seed/seed.dart';
import '../consts.dart';
import '../models/models.dart';
import '../utils/utils.dart';
class InsomniaIO {
List<(String?, HttpRequestModel)>? getHttpRequestModelList(String content) {

View File

@ -1,8 +1,5 @@
import 'package:better_networking/better_networking.dart';
import 'package:postman/postman.dart' as pm;
import 'package:seed/seed.dart';
import '../consts.dart';
import '../models/models.dart';
import '../utils/utils.dart';
class PostmanIO {
List<(String?, HttpRequestModel)>? getHttpRequestModelList(String content) {

View File

@ -1,68 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'http_request_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$HttpRequestModelImpl _$$HttpRequestModelImplFromJson(Map json) =>
_$HttpRequestModelImpl(
method: $enumDecodeNullable(_$HTTPVerbEnumMap, json['method']) ??
HTTPVerb.get,
url: json['url'] as String? ?? "",
headers: (json['headers'] as List<dynamic>?)
?.map((e) =>
NameValueModel.fromJson(Map<String, Object?>.from(e as Map)))
.toList(),
params: (json['params'] as List<dynamic>?)
?.map((e) =>
NameValueModel.fromJson(Map<String, Object?>.from(e as Map)))
.toList(),
isHeaderEnabledList: (json['isHeaderEnabledList'] as List<dynamic>?)
?.map((e) => e as bool)
.toList(),
isParamEnabledList: (json['isParamEnabledList'] as List<dynamic>?)
?.map((e) => e as bool)
.toList(),
bodyContentType:
$enumDecodeNullable(_$ContentTypeEnumMap, json['bodyContentType']) ??
ContentType.json,
body: json['body'] as String?,
query: json['query'] as String?,
formData: (json['formData'] as List<dynamic>?)
?.map((e) =>
FormDataModel.fromJson(Map<String, Object?>.from(e as Map)))
.toList(),
);
Map<String, dynamic> _$$HttpRequestModelImplToJson(
_$HttpRequestModelImpl instance) =>
<String, dynamic>{
'method': _$HTTPVerbEnumMap[instance.method]!,
'url': instance.url,
'headers': instance.headers?.map((e) => e.toJson()).toList(),
'params': instance.params?.map((e) => e.toJson()).toList(),
'isHeaderEnabledList': instance.isHeaderEnabledList,
'isParamEnabledList': instance.isParamEnabledList,
'bodyContentType': _$ContentTypeEnumMap[instance.bodyContentType]!,
'body': instance.body,
'query': instance.query,
'formData': instance.formData?.map((e) => e.toJson()).toList(),
};
const _$HTTPVerbEnumMap = {
HTTPVerb.get: 'get',
HTTPVerb.head: 'head',
HTTPVerb.post: 'post',
HTTPVerb.put: 'put',
HTTPVerb.patch: 'patch',
HTTPVerb.delete: 'delete',
HTTPVerb.options: 'options',
};
const _$ContentTypeEnumMap = {
ContentType.json: 'json',
ContentType.text: 'text',
ContentType.formdata: 'formdata',
};

View File

@ -1,3 +1 @@
export 'environment_model.dart';
export 'http_request_model.dart';
export 'http_response_model.dart';

View File

@ -1,6 +1 @@
export 'content_type_utils.dart';
export 'graphql_utils.dart';
export 'http_request_utils.dart';
export 'http_response_utils.dart';
export 'string_utils.dart';
export 'uri_utils.dart';

View File

@ -11,20 +11,17 @@ environment:
dependencies:
flutter:
sdk: flutter
collection: ^1.18.0
better_networking:
path: ../better_networking
curl_parser:
path: ../curl_parser
freezed_annotation: ^2.4.1
http: ^1.3.0
http_parser: ^4.1.2
insomnia_collection:
path: ../insomnia_collection
json5: ^0.8.2
postman:
path: ../postman
har:
path: ../har
seed: ^0.0.3
insomnia_collection:
path: ../insomnia_collection
postman:
path: ../postman
xml: ^6.3.0
dev_dependencies:

View File

@ -1,5 +1,7 @@
# melos_managed_dependency_overrides: curl_parser,insomnia_collection,postman,seed,har
# melos_managed_dependency_overrides: better_networking,curl_parser,har,insomnia_collection,postman,seed
dependency_overrides:
better_networking:
path: ../better_networking
curl_parser:
path: ../curl_parser
har:

32
packages/better_networking/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
build/
coverage/

View File

@ -0,0 +1,10 @@
pubspec.lock
melos_better_networking.iml
build/
coverage/
dart_test.yaml
doc/
test/
pubspec_overrides.yaml
better_networking_example/melos_better_networking_example.iml
better_networking_example/pubspec_overrides.yaml

View File

@ -0,0 +1,3 @@
## 0.0.1
* Intial release.

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Ashita Prasad, Ankit Mahato
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,73 @@
## better_networking
### Making HTTP Requests
```dart
import 'package:better_networking/better_networking.dart';
final (resp, duration, err) = await sendHttpRequest(
'Request1',
APIType.rest,
HttpRequestModel(
url: 'https://example.com',
method: HTTPVerb.post,
headers: [
NameValueModel(
name: 'x-api-key',
value: 'AeAze8493ufhd9....',
),
],
params: [NameValueModel(name: 'version', value: 'v1')],
query: 'users',
body: jsonEncode({"name": "morpheus", "job": "leader"}),
formData: [
FormDataModel(
name: 'name',
value: 'morpheus',
type: FormDataType.text,
),
],
),
);
//Similarly, Requests can be made for all the types of requests
```
### Making Streaming Requests (SSE)
```dart
import 'package:better_networking/better_networking.dart';
final stream = await streamHttpRequest(
'S1',
APIType.rest,
HttpRequestModel(
method: HTTPVerb.get,
url: 'http://example.com',
body: jsonEncode({...}),
),
);
stream.listen(
(data) {
print('Recieved Data: $data');
},
onDone: () {
print('Streaming Complete');
},
onError: (e) {
print(e);
},
);
```
### Cancelling Requests
```dart
import 'package:better_networking/better_networking.dart';
cancelHttpRequest('request-id');
```
### Make GraphQL Requests
```dart
APIType.graphql
```

View File

@ -0,0 +1,8 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
invalid_annotation_target: ignore
exclude:
- "**/*.freezed.dart"
- "**/*.g.dart"

View File

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -0,0 +1,64 @@
# better_networking example
### Making HTTP Requests
```dart
final (resp, duration, err) = await sendHttpRequest(
'Request1',
APIType.rest,
HttpRequestModel(
url: 'https://example.com',
method: HTTPVerb.post,
headers: [
NameValueModel(
name: 'x-api-key',
value: 'AeAze8493ufhd9....',
),
],
params: [NameValueModel(name: 'version', value: 'v1')],
query: 'users',
body: jsonEncode({"name": "morpheus", "job": "leader"}),
formData: [
FormDataModel(
name: 'name',
value: 'morpheus',
type: FormDataType.text,
),
],
),
);
//Similarly, Requests can be made for all the types of requests
```
### Making Streaming Requests (SSE)
```dart
final stream = await streamHttpRequest(
'S1',
APIType.rest,
HttpRequestModel(
method: HTTPVerb.get,
url: 'http://example.com',
body: jsonEncode({...}),
),
);
stream.listen(
(data) {
print('Recieved Data: $data');
},
onDone: () {
print('Streaming Complete');
},
onError: (e) {
print(e);
},
);
```
### Cancelling Requests
```dart
cancelHttpRequest('request-id');
```
### Make GraphQL Requsts
```dart
APIType.graphql
```

View File

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,146 @@
import 'dart:convert';
import 'package:better_networking/better_networking.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const ExampleApplication());
}
class ExampleApplication extends StatelessWidget {
const ExampleApplication({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Better Networking ',
home: BetterNetworkingExample(),
);
}
}
class BetterNetworkingExample extends StatefulWidget {
const BetterNetworkingExample({super.key});
@override
State<BetterNetworkingExample> createState() =>
_BetterNetworkingExampleState();
}
class _BetterNetworkingExampleState extends State<BetterNetworkingExample> {
String currentRequest = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Better Networking Example')),
body: Container(
padding: EdgeInsets.all(20),
child: Column(
children: [
ElevatedButton(
onPressed: () {
if (currentRequest.isEmpty) return;
debugPrint('Cancelling: $currentRequest');
cancelHttpRequest(currentRequest);
},
child: Text('CANCEL REQUEST'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
setState(() {
currentRequest = 'G1';
});
final (resp, duration, err) = await sendHttpRequest(
'G1',
APIType.rest,
HttpRequestModel(
url: 'https://reqres.in/api/users/2',
method: HTTPVerb.get,
headers: [
NameValueModel(
name: 'x-api-key',
value: 'reqres-free-v1',
),
],
),
);
debugPrint('Response: ${resp?.body}');
debugPrint('Duration: ${duration?.inMilliseconds}');
debugPrint('Error: $err');
},
child: Text('GET REQUEST'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
setState(() {
currentRequest = 'P1';
});
final (resp, duration, err) = await sendHttpRequest(
'P1',
APIType.rest,
HttpRequestModel(
url: 'https://reqres.in/api/users',
method: HTTPVerb.post,
headers: [
NameValueModel(
name: 'x-api-key',
value: 'reqres-free-v1',
),
],
body: jsonEncode({"name": "morpheus", "job": "leader"}),
),
);
debugPrint('Response: ${resp?.body}');
debugPrint('Duration: ${duration?.inMilliseconds}');
debugPrint('Error: $err');
},
child: Text('POST REQUEST'),
),
SizedBox(height: 50),
ElevatedButton(
onPressed: () async {
setState(() {
currentRequest = 'S1';
});
final stream = await streamHttpRequest(
'S1',
APIType.rest,
HttpRequestModel(
method: HTTPVerb.post,
url: 'http://localhost:11434/v1/chat/completions',
body: jsonEncode({
'model': 'gemma3:latest',
'stream': true,
"messages": [
{
"role": "system",
"content":
'Give me a 200 word essay about the given topic',
},
{"role": "user", "content": 'Flutter'},
],
}),
),
);
stream.listen(
(data) {
debugPrint('Recieved Data: $data');
},
onDone: () {
debugPrint('Streaming Complete');
},
onError: (e) {
debugPrint(e);
},
);
},
child: Text('STREAM REQUEST'),
),
],
),
),
);
}
}

View File

@ -0,0 +1,299 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
better_networking:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
url: "https://pub.dev"
source: hosted
version: "2.4.4"
http:
dependency: transitive
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
json5:
dependency: transitive
description:
name: json5
sha256: b67d6e06c9e225c8277d3c43f796677af7975a2a2b0669ff12ba38ff466a31f4
url: "https://pub.dev"
source: hosted
version: "0.8.2"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
seed:
dependency: "direct overridden"
description:
path: "../../seed"
relative: true
source: path
version: "0.0.3"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@ -0,0 +1,23 @@
name: better_networking_example
description: "A new Flutter project."
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ^3.8.0
dependencies:
flutter:
sdk: flutter
better_networking:
path: ..
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true

View File

@ -0,0 +1,6 @@
# melos_managed_dependency_overrides: better_networking,seed
dependency_overrides:
better_networking:
path: ..
seed:
path: ../../seed

View File

@ -0,0 +1,13 @@
library better_networking;
export 'consts.dart';
export 'extensions/extensions.dart';
export 'models/models.dart';
export 'utils/utils.dart';
export 'services/services.dart';
// Export 3rd party packages
export 'package:collection/collection.dart';
export 'package:http/http.dart';
export 'package:http_parser/http_parser.dart';
export 'package:seed/seed.dart';

View File

@ -0,0 +1,99 @@
import 'dart:convert';
enum APIType {
rest("HTTP", "HTTP"),
graphql("GraphQL", "GQL");
const APIType(this.label, this.abbr);
final String label;
final String abbr;
}
enum HTTPVerb {
get("GET"),
head("HEAD"),
post("POST"),
put("PUT"),
patch("PAT"),
delete("DEL"),
options("OPT");
const HTTPVerb(this.abbr);
final String abbr;
}
enum SupportedUriSchemes { https, http }
final kSupportedUriSchemes = SupportedUriSchemes.values
.map((i) => i.name)
.toList();
const kDefaultUriScheme = SupportedUriSchemes.https;
final kLocalhostRegex = RegExp(r'^localhost(:\d+)?(/.*)?$');
final kIPHostRegex = RegExp(
r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(:\d+)?(/.*)?$',
);
const kMethodsWithBody = [
HTTPVerb.post,
HTTPVerb.put,
HTTPVerb.patch,
HTTPVerb.delete,
HTTPVerb.options,
];
const kDefaultHttpMethod = HTTPVerb.get;
const kDefaultContentType = ContentType.json;
const kTypeApplication = 'application';
// application
const kSubTypeJson = 'json';
const kSubTypeOctetStream = 'octet-stream';
const kSubTypePdf = 'pdf';
const kSubTypeSql = 'sql';
const kSubTypeXml = 'xml';
const kSubTypeYaml = 'yaml';
const kSubTypeXYaml = 'x-yaml';
const kSubTypeYml = 'x-yml';
const kSubTypeXWwwFormUrlencoded = 'x-www-form-urlencoded';
const kTypeText = 'text';
// text
const kSubTypeCss = 'css';
const kSubTypeCsv = 'csv';
const kSubTypeHtml = 'html';
const kSubTypeJavascript = 'javascript';
const kSubTypeMarkdown = 'markdown';
const kSubTypePlain = 'plain';
const kSubTypeTextXml = 'xml';
const kSubTypeTextYaml = 'yaml';
const kSubTypeTextYml = 'yml';
const kTypeImage = 'image';
//image
const kSubTypeSvg = 'svg+xml';
const kTypeAudio = 'audio';
const kTypeVideo = 'video';
const kTypeMultipart = "multipart";
const kSubTypeFormData = "form-data";
const kSubTypeDefaultViewOptions = 'all';
enum ContentType {
json("$kTypeApplication/$kSubTypeJson"),
text("$kTypeText/$kSubTypePlain"),
formdata("$kTypeMultipart/$kSubTypeFormData");
const ContentType(this.header);
final String header;
}
const JsonEncoder kJsonEncoder = JsonEncoder.withIndent(' ');
const JsonDecoder kJsonDecoder = JsonDecoder();
const LineSplitter kSplitter = LineSplitter();
const kCodeCharsPerLineLimit = 200;
const kHeaderContentType = "Content-Type";
const kMsgRequestCancelled = 'Request Cancelled';

View File

@ -0,0 +1 @@
export 'map_extensions.dart';

View File

@ -2,9 +2,11 @@ import 'dart:io';
extension MapExtension on Map {
bool hasKeyContentType() {
return keys.any((k) => (k is String)
? k.toLowerCase() == HttpHeaders.contentTypeHeader
: false);
return keys.any(
(k) => (k is String)
? k.toLowerCase() == HttpHeaders.contentTypeHeader
: false,
);
}
String? getKeyContentType() {
@ -13,9 +15,11 @@ extension MapExtension on Map {
}
bool present = hasKeyContentType();
if (present) {
return keys.firstWhere((e) => (e is String)
? e.toLowerCase() == HttpHeaders.contentTypeHeader
: false);
return keys.firstWhere(
(e) => (e is String)
? e.toLowerCase() == HttpHeaders.contentTypeHeader
: false,
);
}
return null;
}
@ -26,7 +30,8 @@ extension MapExtension on Map {
Map removeKeyContentType() {
removeWhere(
(key, value) => key.toLowerCase() == HttpHeaders.contentTypeHeader);
(key, value) => key.toLowerCase() == HttpHeaders.contentTypeHeader,
);
return this;
}
}

View File

@ -13,10 +13,7 @@ part 'http_request_model.g.dart';
class HttpRequestModel with _$HttpRequestModel {
const HttpRequestModel._();
@JsonSerializable(
explicitToJson: true,
anyMap: true,
)
@JsonSerializable(explicitToJson: true, anyMap: true)
const factory HttpRequestModel({
@Default(HTTPVerb.get) HTTPVerb method,
@Default("") String url,

View File

@ -12,7 +12,8 @@ part of 'http_request_model.dart';
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
HttpRequestModel _$HttpRequestModelFromJson(Map<String, dynamic> json) {
return _HttpRequestModel.fromJson(json);
@ -44,20 +45,22 @@ mixin _$HttpRequestModel {
/// @nodoc
abstract class $HttpRequestModelCopyWith<$Res> {
factory $HttpRequestModelCopyWith(
HttpRequestModel value, $Res Function(HttpRequestModel) then) =
_$HttpRequestModelCopyWithImpl<$Res, HttpRequestModel>;
HttpRequestModel value,
$Res Function(HttpRequestModel) then,
) = _$HttpRequestModelCopyWithImpl<$Res, HttpRequestModel>;
@useResult
$Res call(
{HTTPVerb method,
String url,
List<NameValueModel>? headers,
List<NameValueModel>? params,
List<bool>? isHeaderEnabledList,
List<bool>? isParamEnabledList,
ContentType bodyContentType,
String? body,
String? query,
List<FormDataModel>? formData});
$Res call({
HTTPVerb method,
String url,
List<NameValueModel>? headers,
List<NameValueModel>? params,
List<bool>? isHeaderEnabledList,
List<bool>? isParamEnabledList,
ContentType bodyContentType,
String? body,
String? query,
List<FormDataModel>? formData,
});
}
/// @nodoc
@ -86,79 +89,85 @@ class _$HttpRequestModelCopyWithImpl<$Res, $Val extends HttpRequestModel>
Object? query = freezed,
Object? formData = freezed,
}) {
return _then(_value.copyWith(
method: null == method
? _value.method
: method // ignore: cast_nullable_to_non_nullable
as HTTPVerb,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
headers: freezed == headers
? _value.headers
: headers // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
params: freezed == params
? _value.params
: params // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
isHeaderEnabledList: freezed == isHeaderEnabledList
? _value.isHeaderEnabledList
: isHeaderEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
isParamEnabledList: freezed == isParamEnabledList
? _value.isParamEnabledList
: isParamEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
bodyContentType: null == bodyContentType
? _value.bodyContentType
: bodyContentType // ignore: cast_nullable_to_non_nullable
as ContentType,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
query: freezed == query
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String?,
formData: freezed == formData
? _value.formData
: formData // ignore: cast_nullable_to_non_nullable
as List<FormDataModel>?,
) as $Val);
return _then(
_value.copyWith(
method: null == method
? _value.method
: method // ignore: cast_nullable_to_non_nullable
as HTTPVerb,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
headers: freezed == headers
? _value.headers
: headers // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
params: freezed == params
? _value.params
: params // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
isHeaderEnabledList: freezed == isHeaderEnabledList
? _value.isHeaderEnabledList
: isHeaderEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
isParamEnabledList: freezed == isParamEnabledList
? _value.isParamEnabledList
: isParamEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
bodyContentType: null == bodyContentType
? _value.bodyContentType
: bodyContentType // ignore: cast_nullable_to_non_nullable
as ContentType,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
query: freezed == query
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String?,
formData: freezed == formData
? _value.formData
: formData // ignore: cast_nullable_to_non_nullable
as List<FormDataModel>?,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$HttpRequestModelImplCopyWith<$Res>
implements $HttpRequestModelCopyWith<$Res> {
factory _$$HttpRequestModelImplCopyWith(_$HttpRequestModelImpl value,
$Res Function(_$HttpRequestModelImpl) then) =
__$$HttpRequestModelImplCopyWithImpl<$Res>;
factory _$$HttpRequestModelImplCopyWith(
_$HttpRequestModelImpl value,
$Res Function(_$HttpRequestModelImpl) then,
) = __$$HttpRequestModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{HTTPVerb method,
String url,
List<NameValueModel>? headers,
List<NameValueModel>? params,
List<bool>? isHeaderEnabledList,
List<bool>? isParamEnabledList,
ContentType bodyContentType,
String? body,
String? query,
List<FormDataModel>? formData});
$Res call({
HTTPVerb method,
String url,
List<NameValueModel>? headers,
List<NameValueModel>? params,
List<bool>? isHeaderEnabledList,
List<bool>? isParamEnabledList,
ContentType bodyContentType,
String? body,
String? query,
List<FormDataModel>? formData,
});
}
/// @nodoc
class __$$HttpRequestModelImplCopyWithImpl<$Res>
extends _$HttpRequestModelCopyWithImpl<$Res, _$HttpRequestModelImpl>
implements _$$HttpRequestModelImplCopyWith<$Res> {
__$$HttpRequestModelImplCopyWithImpl(_$HttpRequestModelImpl _value,
$Res Function(_$HttpRequestModelImpl) _then)
: super(_value, _then);
__$$HttpRequestModelImplCopyWithImpl(
_$HttpRequestModelImpl _value,
$Res Function(_$HttpRequestModelImpl) _then,
) : super(_value, _then);
/// Create a copy of HttpRequestModel
/// with the given fields replaced by the non-null parameter values.
@ -176,48 +185,50 @@ class __$$HttpRequestModelImplCopyWithImpl<$Res>
Object? query = freezed,
Object? formData = freezed,
}) {
return _then(_$HttpRequestModelImpl(
method: null == method
? _value.method
: method // ignore: cast_nullable_to_non_nullable
as HTTPVerb,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
headers: freezed == headers
? _value._headers
: headers // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
params: freezed == params
? _value._params
: params // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
isHeaderEnabledList: freezed == isHeaderEnabledList
? _value._isHeaderEnabledList
: isHeaderEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
isParamEnabledList: freezed == isParamEnabledList
? _value._isParamEnabledList
: isParamEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
bodyContentType: null == bodyContentType
? _value.bodyContentType
: bodyContentType // ignore: cast_nullable_to_non_nullable
as ContentType,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
query: freezed == query
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String?,
formData: freezed == formData
? _value._formData
: formData // ignore: cast_nullable_to_non_nullable
as List<FormDataModel>?,
));
return _then(
_$HttpRequestModelImpl(
method: null == method
? _value.method
: method // ignore: cast_nullable_to_non_nullable
as HTTPVerb,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
headers: freezed == headers
? _value._headers
: headers // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
params: freezed == params
? _value._params
: params // ignore: cast_nullable_to_non_nullable
as List<NameValueModel>?,
isHeaderEnabledList: freezed == isHeaderEnabledList
? _value._isHeaderEnabledList
: isHeaderEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
isParamEnabledList: freezed == isParamEnabledList
? _value._isParamEnabledList
: isParamEnabledList // ignore: cast_nullable_to_non_nullable
as List<bool>?,
bodyContentType: null == bodyContentType
? _value.bodyContentType
: bodyContentType // ignore: cast_nullable_to_non_nullable
as ContentType,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
query: freezed == query
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String?,
formData: freezed == formData
? _value._formData
: formData // ignore: cast_nullable_to_non_nullable
as List<FormDataModel>?,
),
);
}
}
@ -225,23 +236,23 @@ class __$$HttpRequestModelImplCopyWithImpl<$Res>
@JsonSerializable(explicitToJson: true, anyMap: true)
class _$HttpRequestModelImpl extends _HttpRequestModel {
const _$HttpRequestModelImpl(
{this.method = HTTPVerb.get,
this.url = "",
final List<NameValueModel>? headers,
final List<NameValueModel>? params,
final List<bool>? isHeaderEnabledList,
final List<bool>? isParamEnabledList,
this.bodyContentType = ContentType.json,
this.body,
this.query,
final List<FormDataModel>? formData})
: _headers = headers,
_params = params,
_isHeaderEnabledList = isHeaderEnabledList,
_isParamEnabledList = isParamEnabledList,
_formData = formData,
super._();
const _$HttpRequestModelImpl({
this.method = HTTPVerb.get,
this.url = "",
final List<NameValueModel>? headers,
final List<NameValueModel>? params,
final List<bool>? isHeaderEnabledList,
final List<bool>? isParamEnabledList,
this.bodyContentType = ContentType.json,
this.body,
this.query,
final List<FormDataModel>? formData,
}) : _headers = headers,
_params = params,
_isHeaderEnabledList = isHeaderEnabledList,
_isParamEnabledList = isParamEnabledList,
_formData = formData,
super._();
factory _$HttpRequestModelImpl.fromJson(Map<String, dynamic> json) =>
_$$HttpRequestModelImplFromJson(json);
@ -325,10 +336,14 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
(identical(other.url, url) || other.url == url) &&
const DeepCollectionEquality().equals(other._headers, _headers) &&
const DeepCollectionEquality().equals(other._params, _params) &&
const DeepCollectionEquality()
.equals(other._isHeaderEnabledList, _isHeaderEnabledList) &&
const DeepCollectionEquality()
.equals(other._isParamEnabledList, _isParamEnabledList) &&
const DeepCollectionEquality().equals(
other._isHeaderEnabledList,
_isHeaderEnabledList,
) &&
const DeepCollectionEquality().equals(
other._isParamEnabledList,
_isParamEnabledList,
) &&
(identical(other.bodyContentType, bodyContentType) ||
other.bodyContentType == bodyContentType) &&
(identical(other.body, body) || other.body == body) &&
@ -339,17 +354,18 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
method,
url,
const DeepCollectionEquality().hash(_headers),
const DeepCollectionEquality().hash(_params),
const DeepCollectionEquality().hash(_isHeaderEnabledList),
const DeepCollectionEquality().hash(_isParamEnabledList),
bodyContentType,
body,
query,
const DeepCollectionEquality().hash(_formData));
runtimeType,
method,
url,
const DeepCollectionEquality().hash(_headers),
const DeepCollectionEquality().hash(_params),
const DeepCollectionEquality().hash(_isHeaderEnabledList),
const DeepCollectionEquality().hash(_isParamEnabledList),
bodyContentType,
body,
query,
const DeepCollectionEquality().hash(_formData),
);
/// Create a copy of HttpRequestModel
/// with the given fields replaced by the non-null parameter values.
@ -358,28 +374,29 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
@pragma('vm:prefer-inline')
_$$HttpRequestModelImplCopyWith<_$HttpRequestModelImpl> get copyWith =>
__$$HttpRequestModelImplCopyWithImpl<_$HttpRequestModelImpl>(
this, _$identity);
this,
_$identity,
);
@override
Map<String, dynamic> toJson() {
return _$$HttpRequestModelImplToJson(
this,
);
return _$$HttpRequestModelImplToJson(this);
}
}
abstract class _HttpRequestModel extends HttpRequestModel {
const factory _HttpRequestModel(
{final HTTPVerb method,
final String url,
final List<NameValueModel>? headers,
final List<NameValueModel>? params,
final List<bool>? isHeaderEnabledList,
final List<bool>? isParamEnabledList,
final ContentType bodyContentType,
final String? body,
final String? query,
final List<FormDataModel>? formData}) = _$HttpRequestModelImpl;
const factory _HttpRequestModel({
final HTTPVerb method,
final String url,
final List<NameValueModel>? headers,
final List<NameValueModel>? params,
final List<bool>? isHeaderEnabledList,
final List<bool>? isParamEnabledList,
final ContentType bodyContentType,
final String? body,
final String? query,
final List<FormDataModel>? formData,
}) = _$HttpRequestModelImpl;
const _HttpRequestModel._() : super._();
factory _HttpRequestModel.fromJson(Map<String, dynamic> json) =

View File

@ -0,0 +1,66 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'http_request_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$HttpRequestModelImpl _$$HttpRequestModelImplFromJson(
Map json,
) => _$HttpRequestModelImpl(
method:
$enumDecodeNullable(_$HTTPVerbEnumMap, json['method']) ?? HTTPVerb.get,
url: json['url'] as String? ?? "",
headers: (json['headers'] as List<dynamic>?)
?.map((e) => NameValueModel.fromJson(Map<String, Object?>.from(e as Map)))
.toList(),
params: (json['params'] as List<dynamic>?)
?.map((e) => NameValueModel.fromJson(Map<String, Object?>.from(e as Map)))
.toList(),
isHeaderEnabledList: (json['isHeaderEnabledList'] as List<dynamic>?)
?.map((e) => e as bool)
.toList(),
isParamEnabledList: (json['isParamEnabledList'] as List<dynamic>?)
?.map((e) => e as bool)
.toList(),
bodyContentType:
$enumDecodeNullable(_$ContentTypeEnumMap, json['bodyContentType']) ??
ContentType.json,
body: json['body'] as String?,
query: json['query'] as String?,
formData: (json['formData'] as List<dynamic>?)
?.map((e) => FormDataModel.fromJson(Map<String, Object?>.from(e as Map)))
.toList(),
);
Map<String, dynamic> _$$HttpRequestModelImplToJson(
_$HttpRequestModelImpl instance,
) => <String, dynamic>{
'method': _$HTTPVerbEnumMap[instance.method]!,
'url': instance.url,
'headers': instance.headers?.map((e) => e.toJson()).toList(),
'params': instance.params?.map((e) => e.toJson()).toList(),
'isHeaderEnabledList': instance.isHeaderEnabledList,
'isParamEnabledList': instance.isParamEnabledList,
'bodyContentType': _$ContentTypeEnumMap[instance.bodyContentType]!,
'body': instance.body,
'query': instance.query,
'formData': instance.formData?.map((e) => e.toJson()).toList(),
};
const _$HTTPVerbEnumMap = {
HTTPVerb.get: 'get',
HTTPVerb.head: 'head',
HTTPVerb.post: 'post',
HTTPVerb.put: 'put',
HTTPVerb.patch: 'patch',
HTTPVerb.delete: 'delete',
HTTPVerb.options: 'options',
};
const _$ContentTypeEnumMap = {
ContentType.json: 'json',
ContentType.text: 'text',
ContentType.formdata: 'formdata',
};

View File

@ -2,12 +2,12 @@ import 'dart:io';
import 'dart:convert';
import 'dart:typed_data';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:collection/collection.dart' show mergeMaps;
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import '../extensions/extensions.dart';
import '../utils/utils.dart';
import '../consts.dart';
import 'package:collection/collection.dart' show mergeMaps;
part 'http_response_model.freezed.dart';
part 'http_response_model.g.dart';
@ -44,10 +44,7 @@ class DurationConverter implements JsonConverter<Duration?, int?> {
class HttpResponseModel with _$HttpResponseModel {
const HttpResponseModel._();
@JsonSerializable(
explicitToJson: true,
anyMap: true,
)
@JsonSerializable(explicitToJson: true, anyMap: true)
const factory HttpResponseModel({
int? statusCode,
Map<String, String>? headers,
@ -64,13 +61,10 @@ class HttpResponseModel with _$HttpResponseModel {
String? get contentType => headers?.getValueContentType();
MediaType? get mediaType => getMediaTypeFromHeaders(headers);
HttpResponseModel fromResponse({
required Response response,
Duration? time,
}) {
final responseHeaders = mergeMaps(
{HttpHeaders.contentLengthHeader: response.contentLength.toString()},
response.headers);
HttpResponseModel fromResponse({required Response response, Duration? time}) {
final responseHeaders = mergeMaps({
HttpHeaders.contentLengthHeader: response.contentLength.toString(),
}, response.headers);
MediaType? mediaType = getMediaTypeFromHeaders(responseHeaders);
final body = (mediaType?.subtype == kSubTypeJson)
? utf8.decode(response.bodyBytes)

View File

@ -12,7 +12,8 @@ part of 'http_response_model.dart';
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
HttpResponseModel _$HttpResponseModelFromJson(Map<String, dynamic> json) {
return _HttpResponseModel.fromJson(json);
@ -43,17 +44,19 @@ mixin _$HttpResponseModel {
/// @nodoc
abstract class $HttpResponseModelCopyWith<$Res> {
factory $HttpResponseModelCopyWith(
HttpResponseModel value, $Res Function(HttpResponseModel) then) =
_$HttpResponseModelCopyWithImpl<$Res, HttpResponseModel>;
HttpResponseModel value,
$Res Function(HttpResponseModel) then,
) = _$HttpResponseModelCopyWithImpl<$Res, HttpResponseModel>;
@useResult
$Res call(
{int? statusCode,
Map<String, String>? headers,
Map<String, String>? requestHeaders,
String? body,
String? formattedBody,
@Uint8ListConverter() Uint8List? bodyBytes,
@DurationConverter() Duration? time});
$Res call({
int? statusCode,
Map<String, String>? headers,
Map<String, String>? requestHeaders,
String? body,
String? formattedBody,
@Uint8ListConverter() Uint8List? bodyBytes,
@DurationConverter() Duration? time,
});
}
/// @nodoc
@ -79,64 +82,70 @@ class _$HttpResponseModelCopyWithImpl<$Res, $Val extends HttpResponseModel>
Object? bodyBytes = freezed,
Object? time = freezed,
}) {
return _then(_value.copyWith(
statusCode: freezed == statusCode
? _value.statusCode
: statusCode // ignore: cast_nullable_to_non_nullable
as int?,
headers: freezed == headers
? _value.headers
: headers // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
requestHeaders: freezed == requestHeaders
? _value.requestHeaders
: requestHeaders // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
formattedBody: freezed == formattedBody
? _value.formattedBody
: formattedBody // ignore: cast_nullable_to_non_nullable
as String?,
bodyBytes: freezed == bodyBytes
? _value.bodyBytes
: bodyBytes // ignore: cast_nullable_to_non_nullable
as Uint8List?,
time: freezed == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as Duration?,
) as $Val);
return _then(
_value.copyWith(
statusCode: freezed == statusCode
? _value.statusCode
: statusCode // ignore: cast_nullable_to_non_nullable
as int?,
headers: freezed == headers
? _value.headers
: headers // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
requestHeaders: freezed == requestHeaders
? _value.requestHeaders
: requestHeaders // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
formattedBody: freezed == formattedBody
? _value.formattedBody
: formattedBody // ignore: cast_nullable_to_non_nullable
as String?,
bodyBytes: freezed == bodyBytes
? _value.bodyBytes
: bodyBytes // ignore: cast_nullable_to_non_nullable
as Uint8List?,
time: freezed == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as Duration?,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$HttpResponseModelImplCopyWith<$Res>
implements $HttpResponseModelCopyWith<$Res> {
factory _$$HttpResponseModelImplCopyWith(_$HttpResponseModelImpl value,
$Res Function(_$HttpResponseModelImpl) then) =
__$$HttpResponseModelImplCopyWithImpl<$Res>;
factory _$$HttpResponseModelImplCopyWith(
_$HttpResponseModelImpl value,
$Res Function(_$HttpResponseModelImpl) then,
) = __$$HttpResponseModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int? statusCode,
Map<String, String>? headers,
Map<String, String>? requestHeaders,
String? body,
String? formattedBody,
@Uint8ListConverter() Uint8List? bodyBytes,
@DurationConverter() Duration? time});
$Res call({
int? statusCode,
Map<String, String>? headers,
Map<String, String>? requestHeaders,
String? body,
String? formattedBody,
@Uint8ListConverter() Uint8List? bodyBytes,
@DurationConverter() Duration? time,
});
}
/// @nodoc
class __$$HttpResponseModelImplCopyWithImpl<$Res>
extends _$HttpResponseModelCopyWithImpl<$Res, _$HttpResponseModelImpl>
implements _$$HttpResponseModelImplCopyWith<$Res> {
__$$HttpResponseModelImplCopyWithImpl(_$HttpResponseModelImpl _value,
$Res Function(_$HttpResponseModelImpl) _then)
: super(_value, _then);
__$$HttpResponseModelImplCopyWithImpl(
_$HttpResponseModelImpl _value,
$Res Function(_$HttpResponseModelImpl) _then,
) : super(_value, _then);
/// Create a copy of HttpResponseModel
/// with the given fields replaced by the non-null parameter values.
@ -151,36 +160,38 @@ class __$$HttpResponseModelImplCopyWithImpl<$Res>
Object? bodyBytes = freezed,
Object? time = freezed,
}) {
return _then(_$HttpResponseModelImpl(
statusCode: freezed == statusCode
? _value.statusCode
: statusCode // ignore: cast_nullable_to_non_nullable
as int?,
headers: freezed == headers
? _value._headers
: headers // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
requestHeaders: freezed == requestHeaders
? _value._requestHeaders
: requestHeaders // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
formattedBody: freezed == formattedBody
? _value.formattedBody
: formattedBody // ignore: cast_nullable_to_non_nullable
as String?,
bodyBytes: freezed == bodyBytes
? _value.bodyBytes
: bodyBytes // ignore: cast_nullable_to_non_nullable
as Uint8List?,
time: freezed == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as Duration?,
));
return _then(
_$HttpResponseModelImpl(
statusCode: freezed == statusCode
? _value.statusCode
: statusCode // ignore: cast_nullable_to_non_nullable
as int?,
headers: freezed == headers
? _value._headers
: headers // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
requestHeaders: freezed == requestHeaders
? _value._requestHeaders
: requestHeaders // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
body: freezed == body
? _value.body
: body // ignore: cast_nullable_to_non_nullable
as String?,
formattedBody: freezed == formattedBody
? _value.formattedBody
: formattedBody // ignore: cast_nullable_to_non_nullable
as String?,
bodyBytes: freezed == bodyBytes
? _value.bodyBytes
: bodyBytes // ignore: cast_nullable_to_non_nullable
as Uint8List?,
time: freezed == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as Duration?,
),
);
}
}
@ -188,17 +199,17 @@ class __$$HttpResponseModelImplCopyWithImpl<$Res>
@JsonSerializable(explicitToJson: true, anyMap: true)
class _$HttpResponseModelImpl extends _HttpResponseModel {
const _$HttpResponseModelImpl(
{this.statusCode,
final Map<String, String>? headers,
final Map<String, String>? requestHeaders,
this.body,
this.formattedBody,
@Uint8ListConverter() this.bodyBytes,
@DurationConverter() this.time})
: _headers = headers,
_requestHeaders = requestHeaders,
super._();
const _$HttpResponseModelImpl({
this.statusCode,
final Map<String, String>? headers,
final Map<String, String>? requestHeaders,
this.body,
this.formattedBody,
@Uint8ListConverter() this.bodyBytes,
@DurationConverter() this.time,
}) : _headers = headers,
_requestHeaders = requestHeaders,
super._();
factory _$HttpResponseModelImpl.fromJson(Map<String, dynamic> json) =>
_$$HttpResponseModelImplFromJson(json);
@ -249,8 +260,10 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
(identical(other.statusCode, statusCode) ||
other.statusCode == statusCode) &&
const DeepCollectionEquality().equals(other._headers, _headers) &&
const DeepCollectionEquality()
.equals(other._requestHeaders, _requestHeaders) &&
const DeepCollectionEquality().equals(
other._requestHeaders,
_requestHeaders,
) &&
(identical(other.body, body) || other.body == body) &&
(identical(other.formattedBody, formattedBody) ||
other.formattedBody == formattedBody) &&
@ -261,14 +274,15 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
statusCode,
const DeepCollectionEquality().hash(_headers),
const DeepCollectionEquality().hash(_requestHeaders),
body,
formattedBody,
const DeepCollectionEquality().hash(bodyBytes),
time);
runtimeType,
statusCode,
const DeepCollectionEquality().hash(_headers),
const DeepCollectionEquality().hash(_requestHeaders),
body,
formattedBody,
const DeepCollectionEquality().hash(bodyBytes),
time,
);
/// Create a copy of HttpResponseModel
/// with the given fields replaced by the non-null parameter values.
@ -277,25 +291,26 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
@pragma('vm:prefer-inline')
_$$HttpResponseModelImplCopyWith<_$HttpResponseModelImpl> get copyWith =>
__$$HttpResponseModelImplCopyWithImpl<_$HttpResponseModelImpl>(
this, _$identity);
this,
_$identity,
);
@override
Map<String, dynamic> toJson() {
return _$$HttpResponseModelImplToJson(
this,
);
return _$$HttpResponseModelImplToJson(this);
}
}
abstract class _HttpResponseModel extends HttpResponseModel {
const factory _HttpResponseModel(
{final int? statusCode,
final Map<String, String>? headers,
final Map<String, String>? requestHeaders,
final String? body,
final String? formattedBody,
@Uint8ListConverter() final Uint8List? bodyBytes,
@DurationConverter() final Duration? time}) = _$HttpResponseModelImpl;
const factory _HttpResponseModel({
final int? statusCode,
final Map<String, String>? headers,
final Map<String, String>? requestHeaders,
final String? body,
final String? formattedBody,
@Uint8ListConverter() final Uint8List? bodyBytes,
@DurationConverter() final Duration? time,
}) = _$HttpResponseModelImpl;
const _HttpResponseModel._() : super._();
factory _HttpResponseModel.fromJson(Map<String, dynamic> json) =

View File

@ -17,19 +17,20 @@ _$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) =>
),
body: json['body'] as String?,
formattedBody: json['formattedBody'] as String?,
bodyBytes:
const Uint8ListConverter().fromJson(json['bodyBytes'] as List<int>?),
bodyBytes: const Uint8ListConverter().fromJson(
json['bodyBytes'] as List<int>?,
),
time: const DurationConverter().fromJson((json['time'] as num?)?.toInt()),
);
Map<String, dynamic> _$$HttpResponseModelImplToJson(
_$HttpResponseModelImpl instance) =>
<String, dynamic>{
'statusCode': instance.statusCode,
'headers': instance.headers,
'requestHeaders': instance.requestHeaders,
'body': instance.body,
'formattedBody': instance.formattedBody,
'bodyBytes': const Uint8ListConverter().toJson(instance.bodyBytes),
'time': const DurationConverter().toJson(instance.time),
};
_$HttpResponseModelImpl instance,
) => <String, dynamic>{
'statusCode': instance.statusCode,
'headers': instance.headers,
'requestHeaders': instance.requestHeaders,
'body': instance.body,
'formattedBody': instance.formattedBody,
'bodyBytes': const Uint8ListConverter().toJson(instance.bodyBytes),
'time': const DurationConverter().toJson(instance.time),
};

View File

@ -0,0 +1,2 @@
export 'http_request_model.dart';
export 'http_response_model.dart';

View File

@ -5,8 +5,8 @@ import 'package:http/io_client.dart';
http.Client createHttpClientWithNoSSL() {
var ioClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
..badCertificateCallback = (X509Certificate cert, String host, int port) =>
true;
return IOClient(ioClient);
}
@ -22,12 +22,10 @@ class HttpClientManager {
HttpClientManager._internal();
http.Client createClient(
String requestId, {
bool noSSL = false,
}) {
final client =
(noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client();
http.Client createClient(String requestId, {bool noSSL = false}) {
final client = (noSSL && !kIsWeb)
? createHttpClientWithNoSSL()
: http.Client();
_clients[requestId] = client;
return client;
}

View File

@ -74,8 +74,9 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
);
}
}
http.StreamedResponse multiPartResponse =
await client.send(multiPartRequest);
http.StreamedResponse multiPartResponse = await client.send(
multiPartRequest,
);
stopwatch.stop();
http.Response convertedMultiPartResponse =
@ -119,11 +120,7 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
}
}
}
response = await client.post(
requestUrl,
headers: headers,
body: body,
);
response = await client.post(requestUrl, headers: headers, body: body);
}
stopwatch.stop();
return (response, stopwatch.elapsed, null);
@ -153,17 +150,145 @@ http.Request prepareHttpRequest({
}) {
var request = http.Request(method, url);
if (headers.getValueContentType() != null) {
request.headers[HttpHeaders.contentTypeHeader] =
headers.getValueContentType()!;
request.headers[HttpHeaders.contentTypeHeader] = headers
.getValueContentType()!;
if (!overrideContentType) {
headers.removeKeyContentType();
}
}
if (body != null) {
request.body = body;
headers[HttpHeaders.contentLengthHeader] =
request.bodyBytes.length.toString();
headers[HttpHeaders.contentLengthHeader] = request.bodyBytes.length
.toString();
}
request.headers.addAll(headers);
return request;
}
Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
String requestId,
APIType apiType,
HttpRequestModel requestModel, {
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
bool noSSL = false,
}) async {
final controller = StreamController<(String?, Duration?, String?)?>();
StreamSubscription<String?>? subscription;
final stopwatch = Stopwatch()..start();
cleanup() async {
stopwatch.stop();
httpClientManager.closeClient(requestId);
await Future.microtask(() {});
controller.close();
}
Future<void> handleError(dynamic error) async {
await Future.microtask(() {});
if (httpClientManager.wasRequestCancelled(requestId)) {
controller.add((null, null, kMsgRequestCancelled));
httpClientManager.removeCancelledRequest(requestId);
} else {
controller.add((null, null, error.toString()));
}
await cleanup();
}
controller.onCancel = () async {
await subscription?.cancel();
httpClientManager.cancelRequest(requestId);
};
if (httpClientManager.wasRequestCancelled(requestId)) {
controller.add((null, null, kMsgRequestCancelled));
httpClientManager.removeCancelledRequest(requestId);
controller.close();
return controller.stream;
}
final client = httpClientManager.createClient(requestId, noSSL: noSSL);
final (uri, uriError) = getValidRequestUri(
requestModel.url,
requestModel.enabledParams,
defaultUriScheme: defaultUriScheme,
);
if (uri == null) {
await handleError(uriError ?? 'Invalid URL');
return controller.stream;
}
final headers = requestModel.enabledHeadersMap;
final hasBody = kMethodsWithBody.contains(requestModel.method);
final isMultipart = requestModel.bodyContentType == ContentType.formdata;
try {
//HANDLE MULTI-PART
if (apiType == APIType.rest && isMultipart && hasBody) {
final multipart = http.MultipartRequest(
requestModel.method.name.toUpperCase(),
uri,
)..headers.addAll(headers);
for (final data in requestModel.formDataList) {
if (data.type == FormDataType.text) {
multipart.fields[data.name] = data.value;
} else {
multipart.files.add(
await http.MultipartFile.fromPath(data.name, data.value),
);
}
}
final streamedResponse = await client.send(multipart);
final stream = streamTextResponse(streamedResponse);
subscription = stream.listen(
(data) => controller.add((data, stopwatch.elapsed, null)),
onDone: () => cleanup(),
onError: handleError,
);
return controller.stream;
}
String? body;
bool overrideContentType = false;
if (hasBody && requestModel.body?.isNotEmpty == true) {
body = requestModel.body;
if (!requestModel.hasContentTypeHeader) {
headers[HttpHeaders.contentTypeHeader] =
requestModel.bodyContentType.header;
} else {
overrideContentType = true;
}
}
final request = prepareHttpRequest(
url: uri,
method: requestModel.method.name.toUpperCase(),
headers: headers,
body: body,
overrideContentType: overrideContentType,
);
final streamedResponse = await client.send(request);
final stream = streamTextResponse(streamedResponse);
subscription = stream.listen(
(data) {
if (!controller.isClosed) {
controller.add((data, stopwatch.elapsed, null));
}
},
onDone: () => cleanup(),
onError: handleError,
);
return controller.stream;
} catch (e) {
await handleError(e);
return controller.stream;
}
}

View File

@ -2,9 +2,7 @@ import 'package:http_parser/http_parser.dart';
import '../consts.dart';
import '../extensions/extensions.dart';
ContentType? getContentTypeFromHeadersMap(
Map<String, String>? kvMap,
) {
ContentType? getContentTypeFromHeadersMap(Map<String, String>? kvMap) {
if (kvMap != null && kvMap.hasKeyContentType()) {
var val = getMediaTypeFromHeaders(kvMap);
return getContentTypeFromMediaType(val);
@ -43,9 +41,7 @@ ContentType? getContentTypeFromMediaType(MediaType? mediaType) {
return null;
}
ContentType? getContentTypeFromContentTypeStr(
String? contentType,
) {
ContentType? getContentTypeFromContentTypeStr(String? contentType) {
if (contentType != null) {
var val = getMediaTypeFromContentType(contentType);
return getContentTypeFromMediaType(val);

View File

@ -3,9 +3,7 @@ import '../models/models.dart';
String? getGraphQLBody(HttpRequestModel httpRequestModel) {
if (httpRequestModel.hasQuery) {
return kJsonEncoder.convert({
"query": httpRequestModel.query,
});
return kJsonEncoder.convert({"query": httpRequestModel.query});
}
return null;
}

View File

@ -1,7 +1,4 @@
import 'package:apidash_core/consts.dart';
import 'package:seed/seed.dart';
import '../models/models.dart';
import 'graphql_utils.dart';
import 'package:better_networking/better_networking.dart';
import 'package:json5/json5.dart' as json5;
Map<String, String>? rowsToMap(
@ -24,9 +21,7 @@ Map<String, String>? rowsToMap(
return finalMap;
}
List<NameValueModel>? mapToRows(
Map<String, String>? kvMap,
) {
List<NameValueModel>? mapToRows(Map<String, String>? kvMap) {
if (kvMap == null) {
return null;
}
@ -37,47 +32,45 @@ List<NameValueModel>? mapToRows(
return finalRows;
}
List<Map<String, String>>? rowsToFormDataMapList(
List<FormDataModel>? kvRows,
) {
List<Map<String, String>>? rowsToFormDataMapList(List<FormDataModel>? kvRows) {
if (kvRows == null) {
return null;
}
List<Map<String, String>> finalMap = kvRows
.map((FormDataModel formData) =>
(formData.name.trim().isEmpty && formData.value.trim().isEmpty)
? null
: {
"name": formData.name,
"value": formData.value,
"type": formData.type.name,
})
.map(
(FormDataModel formData) =>
(formData.name.trim().isEmpty && formData.value.trim().isEmpty)
? null
: {
"name": formData.name,
"value": formData.value,
"type": formData.type.name,
},
)
.nonNulls
.toList();
return finalMap;
}
List<FormDataModel>? mapListToFormDataModelRows(
List<Map>? kvMap,
) {
List<FormDataModel>? mapListToFormDataModelRows(List<Map>? kvMap) {
if (kvMap == null) {
return null;
}
List<FormDataModel> finalRows = kvMap.map(
(formData) {
return FormDataModel(
name: formData["name"],
value: formData["value"],
type: getFormDataType(formData["type"]),
);
},
).toList();
List<FormDataModel> finalRows = kvMap.map((formData) {
return FormDataModel(
name: formData["name"],
value: formData["value"],
type: getFormDataType(formData["type"]),
);
}).toList();
return finalRows;
}
FormDataType getFormDataType(String? type) {
return FormDataType.values.firstWhere((element) => element.name == type,
orElse: () => FormDataType.text);
return FormDataType.values.firstWhere(
(element) => element.name == type,
orElse: () => FormDataType.text,
);
}
List<NameValueModel>? getEnabledRows(
@ -87,8 +80,9 @@ List<NameValueModel>? getEnabledRows(
if (rows == null || isRowEnabledList == null) {
return rows;
}
List<NameValueModel> finalRows =
rows.where((element) => isRowEnabledList[rows.indexOf(element)]).toList();
List<NameValueModel> finalRows = rows
.where((element) => isRowEnabledList[rows.indexOf(element)])
.toList();
return finalRows == [] ? null : finalRows;
}

View File

@ -50,3 +50,20 @@ Future<http.Response> convertStreamedResponse(
return response;
}
Stream<String?> streamTextResponse(
http.StreamedResponse streamedResponse,
) async* {
try {
if (streamedResponse.statusCode != 200) {
final errorText = await streamedResponse.stream.bytesToString();
throw Exception('${streamedResponse.statusCode}\n$errorText');
}
final utf8Stream = streamedResponse.stream.transform(utf8.decoder);
await for (final chunk in utf8Stream) {
yield chunk;
}
} catch (e) {
rethrow;
}
}

View File

@ -0,0 +1,22 @@
import 'dart:math';
class RandomStringGenerator {
static const _chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
static Random rnd = Random();
static String getRandomString(int length) => String.fromCharCodes(
Iterable.generate(
length,
(_) => _chars.codeUnitAt(rnd.nextInt(_chars.length)),
),
);
static String getRandomStringLines(int lines, int length) {
List<String> result = [];
for (var i = 0; i < lines; i++) {
result.add(getRandomString(length));
}
return result.join('\n');
}
}

View File

@ -23,8 +23,10 @@ String stripUrlParams(String url) {
}
(Uri?, String?) getValidRequestUri(
String? url, List<NameValueModel>? requestParams,
{SupportedUriSchemes defaultUriScheme = kDefaultUriScheme}) {
String? url,
List<NameValueModel>? requestParams, {
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
}) {
url = url?.trim();
if (url == null || url == "") {
return (null, "URL is missing!");

View File

@ -0,0 +1,6 @@
export 'content_type_utils.dart';
export 'graphql_utils.dart';
export 'http_request_utils.dart';
export 'http_response_utils.dart';
export 'string_utils.dart' hide RandomStringGenerator;
export 'uri_utils.dart';

View File

@ -0,0 +1,36 @@
name: better_networking
description: "Simplified Networking. Support for sending REST & GraphQL API Requests."
version: 0.0.1
homepage: https://github.com/foss42/apidash
topics:
- networking
- api
- rest
- http
- graphql
environment:
sdk: ^3.8.0
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
collection: ^1.18.0
freezed_annotation: ^2.4.1
http: ^1.3.0
http_parser: ^4.1.2
json5: ^0.8.2
json_annotation: ^4.9.0
seed: ^0.0.3
xml: ^6.3.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.12
flutter_lints: ^4.0.0
freezed: ^2.5.7
json_serializable: ^6.7.1
test: ^1.25.2

View File

@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: seed
dependency_overrides:
seed:
path: ../seed

View File

@ -0,0 +1,6 @@
// ignore_for_file: unused_import
import 'package:flutter_test/flutter_test.dart';
import 'package:better_networking/better_networking.dart';
void main() {}

View File

@ -1,4 +1,4 @@
import 'package:apidash_core/apidash_core.dart';
import 'package:better_networking/better_networking.dart';
import 'package:test/test.dart';
void main() {
@ -112,18 +112,16 @@ void main() {
group("Testing ?.getValueContentType() function", () {
test('Testing ?.getValueContentType() for header1', () {
Map<String, String> header1 = {
"content-type": "application/json",
};
Map<String, String> header1 = {"content-type": "application/json"};
String contentType1Expected = "application/json";
expect(header1.getValueContentType(), contentType1Expected);
});
test('Testing ?.getValueContentType() when header keys are in header case',
() {
Map<String, String> header2 = {
"Content-Type": "application/json",
};
expect(header2.getValueContentType(), "application/json");
});
test(
'Testing ?.getValueContentType() when header keys are in header case',
() {
Map<String, String> header2 = {"Content-Type": "application/json"};
expect(header2.getValueContentType(), "application/json");
},
);
});
}

View File

@ -1,4 +1,4 @@
import 'package:apidash_core/utils/http_request_utils.dart';
import 'package:better_networking/better_networking.dart';
import 'package:test/test.dart';
void main() {

View File

@ -1,5 +1,5 @@
import 'package:apidash_core/utils/utils.dart';
import 'package:http_parser/http_parser.dart';
import 'package:better_networking/better_networking.dart';
import 'package:better_networking/utils/string_utils.dart';
import 'package:test/test.dart';
void main() {
@ -7,8 +7,10 @@ void main() {
test('Testing getMediaTypeFromContentType for json type', () {
String contentType1 = "application/json";
MediaType mediaType1Expected = MediaType("application", "json");
expect(getMediaTypeFromContentType(contentType1).toString(),
mediaType1Expected.toString());
expect(
getMediaTypeFromContentType(contentType1).toString(),
mediaType1Expected.toString(),
);
});
test('Testing getMediaTypeFromContentType for null', () {
expect(getMediaTypeFromContentType(null), null);
@ -16,8 +18,10 @@ void main() {
test('Testing getMediaTypeFromContentType for image svg+xml type', () {
String contentType3 = "image/svg+xml";
MediaType mediaType3Expected = MediaType("image", "svg+xml");
expect(getMediaTypeFromContentType(contentType3).toString(),
mediaType3Expected.toString());
expect(
getMediaTypeFromContentType(contentType3).toString(),
mediaType3Expected.toString(),
);
});
test('Testing getMediaTypeFromContentType for incorrect content type', () {
String contentType4 = "text/html : charset=utf-8";
@ -25,10 +29,13 @@ void main() {
});
test('Testing getMediaTypeFromContentType for text/css type', () {
String contentType5 = "text/css; charset=utf-8";
MediaType mediaType5Expected =
MediaType("text", "css", {"charset": "utf-8"});
expect(getMediaTypeFromContentType(contentType5).toString(),
mediaType5Expected.toString());
MediaType mediaType5Expected = MediaType("text", "css", {
"charset": "utf-8",
});
expect(
getMediaTypeFromContentType(contentType5).toString(),
mediaType5Expected.toString(),
);
});
test('Testing getMediaTypeFromContentType for incorrect with double ;', () {
String contentType6 =
@ -53,11 +60,13 @@ void main() {
Map<String, String> header1 = {
"content-length": "4506",
"cache-control": "private",
"content-type": "application/json"
"content-type": "application/json",
};
MediaType mediaType1Expected = MediaType("application", "json");
expect(getMediaTypeFromHeaders(header1).toString(),
mediaType1Expected.toString());
expect(
getMediaTypeFromHeaders(header1).toString(),
mediaType1Expected.toString(),
);
});
test('Testing getMediaTypeFromHeaders for null header', () {
expect(getMediaTypeFromHeaders(null), null);
@ -71,22 +80,26 @@ void main() {
expect(getMediaTypeFromHeaders(header3), null);
});
test(
'Testing getMediaTypeFromHeaders for erroneous header value - missing type',
() {
Map<String, String> header4 = {"content-type": "/json"};
expect(getMediaTypeFromHeaders(header4), null);
});
'Testing getMediaTypeFromHeaders for erroneous header value - missing type',
() {
Map<String, String> header4 = {"content-type": "/json"};
expect(getMediaTypeFromHeaders(header4), null);
},
);
test(
'Testing getMediaTypeFromHeaders for erroneous header value - missing subtype',
() {
Map<String, String> header5 = {"content-type": "application"};
expect(getMediaTypeFromHeaders(header5), null);
});
'Testing getMediaTypeFromHeaders for erroneous header value - missing subtype',
() {
Map<String, String> header5 = {"content-type": "application"};
expect(getMediaTypeFromHeaders(header5), null);
},
);
test('Testing getMediaTypeFromHeaders for header6', () {
Map<String, String> header6 = {"content-type": "image/svg+xml"};
MediaType mediaType6Expected = MediaType("image", "svg+xml");
expect(getMediaTypeFromHeaders(header6).toString(),
mediaType6Expected.toString());
expect(
getMediaTypeFromHeaders(header6).toString(),
mediaType6Expected.toString(),
);
});
});
@ -157,12 +170,13 @@ void main() {
expect(formatBody(body5, mediaTypeHtml), null);
});
test(
'Testing formatBody for html subtype values with random values within limit',
() {
String body6 =
'''<html>${RandomStringGenerator.getRandomStringLines(100, 190)}</html>''';
expect(formatBody(body6, mediaTypeHtml), body6);
});
'Testing formatBody for html subtype values with random values within limit',
() {
String body6 =
'''<html>${RandomStringGenerator.getRandomStringLines(100, 190)}</html>''';
expect(formatBody(body6, mediaTypeHtml), body6);
},
);
});
});
}

View File

@ -1,15 +1,15 @@
import 'package:apidash_core/utils/uri_utils.dart';
import 'package:seed/seed.dart';
import 'package:better_networking/better_networking.dart';
import 'package:test/test.dart';
void main() {
group("Testing getUriScheme", () {
test('Testing getUriScheme for https', () {
Uri uri1 = Uri(
scheme: 'https',
host: 'dart.dev',
path: 'guides/libraries/library-tour',
fragment: 'numbers');
scheme: 'https',
host: 'dart.dev',
path: 'guides/libraries/library-tour',
fragment: 'numbers',
);
String uriScheme1Expected = 'https';
expect(getUriScheme(uri1), (uriScheme1Expected, true));
});
@ -29,12 +29,14 @@ void main() {
});
group("Testing getValidRequestUri", () {
test('Testing getValidRequestUri with localhost URL without port or path',
() {
String url1 = "localhost";
Uri uri1Expected = Uri(scheme: 'http', host: 'localhost');
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test(
'Testing getValidRequestUri with localhost URL without port or path',
() {
String url1 = "localhost";
Uri uri1Expected = Uri(scheme: 'http', host: 'localhost');
expect(getValidRequestUri(url1, []), (uri1Expected, null));
},
);
test('Testing getValidRequestUri with localhost URL with port', () {
String url1 = "localhost:8080";
@ -42,13 +44,19 @@ void main() {
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test('Testing getValidRequestUri with localhost URL with port and path',
() {
String url1 = "localhost:8080/hello";
Uri uri1Expected =
Uri(scheme: 'http', host: 'localhost', port: 8080, path: '/hello');
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test(
'Testing getValidRequestUri with localhost URL with port and path',
() {
String url1 = "localhost:8080/hello";
Uri uri1Expected = Uri(
scheme: 'http',
host: 'localhost',
port: 8080,
path: '/hello',
);
expect(getValidRequestUri(url1, []), (uri1Expected, null));
},
);
test('Testing getValidRequestUri with localhost URL with http prefix', () {
String url1 = "http://localhost:3080";
@ -76,8 +84,12 @@ void main() {
test('Testing getValidRequestUri with IP URL with port and path', () {
String url1 = "8.8.8.8:8080/hello";
Uri uri1Expected =
Uri(scheme: 'http', host: '8.8.8.8', port: 8080, path: '/hello');
Uri uri1Expected = Uri(
scheme: 'http',
host: '8.8.8.8',
port: 8080,
path: '/hello',
);
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
@ -97,10 +109,11 @@ void main() {
String url1 = "https://api.apidash.dev/country/data";
const kvRow1 = NameValueModel(name: "code", value: "US");
Uri uri1Expected = Uri(
scheme: 'https',
host: 'api.apidash.dev',
path: 'country/data',
queryParameters: {'code': 'US'});
scheme: 'https',
host: 'api.apidash.dev',
path: 'country/data',
queryParameters: {'code': 'US'},
);
expect(getValidRequestUri(url1, [kvRow1]), (uri1Expected, null));
});
test('Testing getValidRequestUri for null url value', () {
@ -115,43 +128,51 @@ void main() {
String url4 = "api.apidash.dev/country/data";
const kvRow4 = NameValueModel(name: "code", value: "US");
Uri uri4Expected = Uri(
scheme: 'https',
host: 'api.apidash.dev',
path: 'country/data',
queryParameters: {'code': 'US'});
scheme: 'https',
host: 'api.apidash.dev',
path: 'country/data',
queryParameters: {'code': 'US'},
);
expect(getValidRequestUri(url4, [kvRow4]), (uri4Expected, null));
});
test('Testing getValidRequestUri when url has fragment', () {
String url5 = "https://dart.dev/guides/libraries/library-tour#numbers";
Uri uri5Expected = Uri(
scheme: 'https',
host: 'dart.dev',
path: '/guides/libraries/library-tour');
scheme: 'https',
host: 'dart.dev',
path: '/guides/libraries/library-tour',
);
expect(getValidRequestUri(url5, null), (uri5Expected, null));
});
test('Testing getValidRequestUri when uri scheme is not supported', () {
String url5 = "mailto:someone@example.com";
expect(getValidRequestUri(url5, null),
(null, "Unsupported URL Scheme (mailto)"));
expect(getValidRequestUri(url5, null), (
null,
"Unsupported URL Scheme (mailto)",
));
});
test('Testing getValidRequestUri when query params in both url and kvrow',
() {
String url6 = "api.apidash.dev/country/data?code=IND";
const kvRow6 = NameValueModel(name: "code", value: "US");
Uri uri6Expected = Uri(
test(
'Testing getValidRequestUri when query params in both url and kvrow',
() {
String url6 = "api.apidash.dev/country/data?code=IND";
const kvRow6 = NameValueModel(name: "code", value: "US");
Uri uri6Expected = Uri(
scheme: 'https',
host: 'api.apidash.dev',
path: 'country/data',
queryParameters: {'code': 'US'});
expect(getValidRequestUri(url6, [kvRow6]), (uri6Expected, null));
});
queryParameters: {'code': 'US'},
);
expect(getValidRequestUri(url6, [kvRow6]), (uri6Expected, null));
},
);
test('Testing getValidRequestUri when kvrow is null', () {
String url7 = "api.apidash.dev/country/data?code=US";
Uri uri7Expected = Uri(
scheme: 'https',
host: 'api.apidash.dev',
path: 'country/data',
queryParameters: {'code': 'US'});
scheme: 'https',
host: 'api.apidash.dev',
path: 'country/data',
queryParameters: {'code': 'US'},
);
expect(getValidRequestUri(url7, null), (uri7Expected, null));
});
});

View File

@ -95,6 +95,13 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.9"
better_networking:
dependency: transitive
description:
path: "packages/better_networking"
relative: true
source: path
version: "0.0.1"
bidi:
dependency: transitive
description:
@ -1981,5 +1988,5 @@ packages:
source: hosted
version: "2.2.2"
sdks:
dart: ">=3.7.0 <3.999.0"
dart: ">=3.8.0 <3.999.0"
flutter: ">=3.29.0"