diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index 281d66ae..38a11911 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,4 +1,8 @@ +import 'dart:io'; +import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart'; import 'kvrow_model.dart'; import '../consts.dart'; @@ -13,6 +17,9 @@ class RequestModel { this.requestParams, this.requestBodyContentType = DEFAULT_BODY_CONTENT_TYPE, this.requestBody, + this.responseStatus, + this.message, + this.responseModel, }); final String id; @@ -23,6 +30,9 @@ class RequestModel { final List? requestParams; final ContentType requestBodyContentType; final dynamic requestBody; + final int? responseStatus; + final String? message; + final ResponseModel? responseModel; RequestModel duplicate({ required String id, @@ -47,6 +57,9 @@ class RequestModel { List? requestParams, ContentType? requestBodyContentType, dynamic requestBody, + int? responseStatus, + String? message, + ResponseModel? responseModel, }) { return RequestModel( id: id ?? this.id, @@ -58,6 +71,9 @@ class RequestModel { requestBodyContentType: requestBodyContentType ?? this.requestBodyContentType, requestBody: requestBody ?? this.requestBody, + responseStatus: responseStatus ?? this.responseStatus, + message: message ?? this.message, + responseModel: responseModel ?? this.responseModel, ); } @@ -72,6 +88,59 @@ class RequestModel { "Request Params: ${requestParams.toString()}", "Request Body Content Type: ${requestBodyContentType.toString()}", "Request Body: ${requestBody.toString()}", + "Response Status: $responseStatus", + "Response Message: $message", + "Response: ${responseModel.toString()}" + ].join("\n"); + } +} + +@immutable +class ResponseModel { + const ResponseModel({ + this.statusCode, + this.headers, + this.requestHeaders, + this.contentType, + this.body, + this.time, + }); + + final int? statusCode; + final Map? headers; + final Map? requestHeaders; + final String? contentType; + final String? body; + final Duration? time; + + ResponseModel fromResponse({ + required Response response, + Duration? time, + }) { + var contentType = response.headers[HttpHeaders.contentTypeHeader]; + final responseHeaders = mergeMaps( + {HttpHeaders.contentLengthHeader: response.contentLength.toString()}, + response.headers); + return ResponseModel( + statusCode: response.statusCode, + headers: responseHeaders, + requestHeaders: response.request?.headers, + contentType: contentType, + body: contentType == JSON_MIMETYPE + ? utf8.decode(response.bodyBytes) + : response.body, + time: time, + ); + } + + @override + String toString() { + return [ + "Response Status: $statusCode", + "Response Time: $time", + "Response Headers: ${headers.toString()}", + "Response Request Headers: ${requestHeaders.toString()}", + "Response Body: $body", ].join("\n"); } } diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 33487d3c..e3361ced 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -1,6 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; import '../models/models.dart'; +import '../services/services.dart'; import '../consts.dart'; const _uuid = Uuid(); @@ -63,20 +64,51 @@ class CollectionStateNotifier extends StateNotifier> { List? requestParams, ContentType? requestBodyContentType, dynamic requestBody, + int? responseStatus, + String? message, + ResponseModel? responseModel, }) { final idx = idxOfId(id); final newModel = state[idx].copyWith( - method: method, - url: url, - requestTabIndex: requestTabIndex, - requestHeaders: requestHeaders, - requestParams: requestParams, - requestBodyContentType: requestBodyContentType, - requestBody: requestBody, - ); + method: method, + url: url, + requestTabIndex: requestTabIndex, + requestHeaders: requestHeaders, + requestParams: requestParams, + requestBodyContentType: requestBodyContentType, + requestBody: requestBody, + responseStatus: responseStatus, + message: message, + responseModel: responseModel); //print(newModel); state = [...state.sublist(0, idx), newModel, ...state.sublist(idx + 1)]; } - Future sendRequest(String id) async {} + Future sendRequest(String id) async { + final idx = idxOfId(id); + RequestModel requestModel = getRequestModel(id); + var responseRec = await request(requestModel); + late final RequestModel newRequestModel; + if (responseRec.$0 == null) { + newRequestModel = requestModel.copyWith( + responseStatus: -1, + message: responseRec.$2, + ); + } else { + final responseModel = ResponseModel() + .fromResponse(response: responseRec.$0!, time: responseRec.$1!); + int statusCode = responseRec.$0!.statusCode; + newRequestModel = requestModel.copyWith( + responseStatus: statusCode, + message: RESPONSE_CODE_REASONS[statusCode], + responseModel: responseModel, + ); + } + print(newRequestModel); + state = [ + ...state.sublist(0, idx), + newRequestModel, + ...state.sublist(idx + 1) + ]; + } } diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart new file mode 100644 index 00000000..ca71c2e6 --- /dev/null +++ b/lib/services/http_service.dart @@ -0,0 +1,130 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter_api_tool/consts.dart'; +import 'package:http/http.dart' as http; +import 'package:collection/collection.dart' show mergeMaps; +import '../models/models.dart'; + +const SUPPORTED_URI_SCHEMES = [ + "https", + "http" +]; + +const DEFAULT_URI_SCHEME = "https://"; + +const METHODS_WITH_BODY = [HTTPVerb.post, HTTPVerb.put, HTTPVerb.patch, HTTPVerb.delete,]; + +(String?, bool) getUriScheme(Uri uri) { + if(uri.hasScheme){ + if(SUPPORTED_URI_SCHEMES.contains(uri.scheme)){ + return (uri.scheme, true); + } + return (uri.scheme, false); + } + return (null, false); +} + +(Uri?, String?) getValidRequestUri(String? url, List? requestParams) { + if(url == null || url.trim() == ""){ + return (null, "URL is missing!"); + } + Uri? uri = Uri.tryParse(url); + if(uri == null){ + return (null, "Check URL (malformed)"); + } + (String?, bool) urlScheme = getUriScheme(uri); + + if(urlScheme.$0 != null){ + if (!urlScheme.$1){ + return (null, "Unsupported URL Scheme (${urlScheme.$0})"); + } + } + else { + url = DEFAULT_URI_SCHEME + url; + } + + uri = Uri.parse(url); + if (uri.hasFragment){ + uri = uri.removeFragment(); + } + + Map? queryParams = rowsToMap(requestParams); + if(queryParams != null){ + if(uri.hasQuery){ + Map urlQueryParams = uri.queryParameters; + queryParams = mergeMaps(urlQueryParams, queryParams); + } + uri = uri.replace(queryParameters: queryParams); + } + return (uri, null); +} + +Future<(http.Response?, Duration?, String?)> request(RequestModel requestModel) async { + (Uri?, String?) uriRec = getValidRequestUri(requestModel.url, + requestModel.requestParams); + if(uriRec.$0 != null){ + Uri requestUrl = uriRec.$0!; + Map headers = rowsToMap(requestModel.requestHeaders) ?? {}; + http.Response response; + String? body; + try { + if(METHODS_WITH_BODY.contains(requestModel.method)){ + if(requestModel.requestBody != null){ + var contentLength = utf8.encode(requestModel.requestBody).length; + if (contentLength > 0){ + body = requestModel.requestBody as String; + headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); + switch(requestModel.requestBodyContentType){ + case ContentType.json: + headers[HttpHeaders.contentTypeHeader] = 'application/json'; + break; + case ContentType.text: + headers[HttpHeaders.contentTypeHeader] = 'text/plain'; + break; + } + } + } + } + Stopwatch stopwatch = Stopwatch()..start(); + switch(requestModel.method){ + case HTTPVerb.get: + response = await http.get(requestUrl, + headers: headers); + break; + case HTTPVerb.head: + response = await http.head(requestUrl, + headers: headers); + break; + case HTTPVerb.post: + response = await http.post(requestUrl, + headers: headers, + body: body); + break; + case HTTPVerb.put: + response = await http.put(requestUrl, + headers: headers, + body: body); + break; + case HTTPVerb.patch: + response = await http.patch(requestUrl, + headers: headers, + body: body); + break; + case HTTPVerb.delete: + response = await http.delete(requestUrl, + headers: headers, + body: body); + break; + } + stopwatch.stop(); + return (response, stopwatch.elapsed, null); + } + catch (e) { + return (null, null, e.toString()); + } + } + else { + return (null, null, uriRec.$1); + } +} diff --git a/lib/services/services.dart b/lib/services/services.dart new file mode 100644 index 00000000..f6a0ec3b --- /dev/null +++ b/lib/services/services.dart @@ -0,0 +1 @@ +export 'http_service.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index f8b5b13e..8b9de4c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,8 @@ dependencies: uuid: ^3.0.7 tab_container: ^2.0.0 davi: ^3.2.0 + http: ^0.13.5 + collection: ^1.17.0 dev_dependencies: flutter_test: