2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 21:46:46 +00:00

Improved GET requests

- Uses custom HttpClient
- Callback for HTTPS certificate errors
- Major code refactor = cleaner code!
- All response validation is performed in the API now!
This commit is contained in:
Oliver Walters 2021-04-19 20:34:12 +10:00
parent d6e9746d8c
commit 71340da068
2 changed files with 151 additions and 194 deletions

View File

@ -165,44 +165,19 @@ class InvenTreeAPI {
print("Connecting to ${apiUrl} -> ${username}:${password}"); print("Connecting to ${apiUrl} -> ${username}:${password}");
var response = await get("").timeout(Duration(seconds: 10)).catchError((error) { // Request the /api/ endpoint - response is a json object
var response = await get("");
print("Error connecting to server: ${error.toString()}");
if (error is SocketException) {
showServerError(
I18N.of(context).connectionRefused,
error.toString());
return null;
} else if (error is TimeoutException) {
showTimeoutError(context);
return null;
} else {
// Unknown error type - re-throw
throw error;
}
});
// Null response means something went horribly wrong!
if (response == null) { if (response == null) {
// Null (or error) response: Show dialog and exit
return false; return false;
} }
if (response.statusCode != 200) {
// Any status code other than 200!
showStatusCodeError(response.statusCode); print("Response from server: ${response}");
// TODO: Interpret the error codes and show custom message?
return false;
}
var data = json.decode(response.body);
print("Response from server: $data");
// We expect certain response from the server // We expect certain response from the server
if (!data.containsKey("server") || !data.containsKey("version") || !data.containsKey("instance")) { if (!response.containsKey("server") || !response.containsKey("version") || !response.containsKey("instance")) {
showServerError( showServerError(
"Missing Data", "Missing Data",
@ -213,11 +188,11 @@ class InvenTreeAPI {
} }
// Record server information // Record server information
_version = data["version"]; _version = response["version"];
instance = data['instance'] ?? ''; instance = response['instance'] ?? '';
// Default API version is 1 if not provided // Default API version is 1 if not provided
_apiVersion = data['apiVersion'] as int ?? 1; _apiVersion = response['apiVersion'] as int ?? 1;
if (_apiVersion < _minApiVersion) { if (_apiVersion < _minApiVersion) {
@ -245,14 +220,7 @@ class InvenTreeAPI {
print("Requesting token from server"); print("Requesting token from server");
response = await get(_URL_GET_TOKEN).timeout(Duration(seconds: 10)).catchError((error) { response = await get(_URL_GET_TOKEN);
print("Error requesting token:");
print(error);
response = null;
});
if (response == null) { if (response == null) {
showServerError( showServerError(
@ -263,13 +231,7 @@ class InvenTreeAPI {
return false; return false;
} }
if (response.statusCode != 200) { if (!response.containsKey("token")) {
showStatusCodeError(response.statusCode);
return false;
} else {
var data = json.decode(response.body);
if (!data.containsKey("token")) {
showServerError( showServerError(
I18N.of(OneContext().context).tokenMissing, I18N.of(OneContext().context).tokenMissing,
"Access token missing from response" "Access token missing from response"
@ -279,7 +241,7 @@ class InvenTreeAPI {
} }
// Return the received token // Return the received token
_token = data["token"]; _token = response["token"];
print("Received token - $_token"); print("Received token - $_token");
// Request user role information // Request user role information
@ -287,7 +249,7 @@ class InvenTreeAPI {
// Ok, probably pretty good... // Ok, probably pretty good...
return true; return true;
};
} }
bool disconnectFromServer() { bool disconnectFromServer() {
@ -348,33 +310,20 @@ class InvenTreeAPI {
// Note: 2021-02-27 this "roles" feature for the API was just introduced. // Note: 2021-02-27 this "roles" feature for the API was just introduced.
// Any 'older' version of the server allows any API method for any logged in user! // Any 'older' version of the server allows any API method for any logged in user!
// We will return immediately, but request the user roles in the background // We will return immediately, but request the user roles in the background
await get(_URL_GET_ROLES).timeout(
Duration(seconds: 10)).catchError((error) {
print("Error requesting roles:");
print(error);
}).then((response) {
print("Response status: ${response.statusCode}"); var response = await get(_URL_GET_ROLES);
if (response.statusCode == 200) { // Null response from server
if (response == null) {
print("null response requesting user roles");
return;
}
// Convert response to JSON representation if (response.containsKey('roles')) {
try {
var data = json.decode(response.body);
if (data.containsKey('roles')) {
// Save a local copy of the user roles // Save a local copy of the user roles
roles = data['roles']; roles = response['roles'];
} }
} }
on FormatException {
// Old server has re-directed away from the API
}
} else {
}
});
}
bool checkPermission(String role, String permission) { bool checkPermission(String role, String permission) {
/* /*
@ -454,11 +403,42 @@ class InvenTreeAPI {
); );
} }
// Perform a GET request HttpClient _client(bool allowBadCert) {
Future<http.Response> get(String url, {Map<String, String> params}) async {
var client = new HttpClient();
client.badCertificateCallback = ((X509Certificate cert, String host, int port) {
// TODO - Introspection of actual certificate?
if (allowBadCert) {
return true;
} else {
showServerError(
I18N.of(OneContext().context).serverCertificateError,
"Server HTTPS certificate invalid"
);
return false;
}
return allowBadCert;
});
// Set the connection timeout
client.connectionTimeout = Duration(seconds: 30);
return client;
}
/**
* Perform a HTTP GET request
* Returns a json object (or null if did not complete)
*/
Future<dynamic> get(String url, {Map<String, String> params}) async {
var _url = makeApiUrl(url); var _url = makeApiUrl(url);
var _headers = defaultHeaders(); var _headers = defaultHeaders();
print("GET: ${_url}");
// If query parameters are supplied, form a query string // If query parameters are supplied, form a query string
if (params != null && params.isNotEmpty) { if (params != null && params.isNotEmpty) {
String query = '?'; String query = '?';
@ -475,7 +455,80 @@ class InvenTreeAPI {
print("GET: " + _url); print("GET: " + _url);
return http.get(_url, headers: _headers); var client = _client(true);
print("Created client");
HttpClientRequest request = await client.getUrl(Uri.parse(_url));
// Set headers
request.headers.set(HttpHeaders.contentTypeHeader, 'application/json');
if (profile != null) {
request.headers.set(
HttpHeaders.authorizationHeader,
_authorizationHeader(profile.username, profile.password)
);
}
print("Created request: ${request.uri}");
HttpClientResponse response = await request.close()
.timeout(Duration(seconds: 30))
.catchError((error) {
print("GET request returned error");
print("URL: ${_url}");
print("Error: ${error.toString()}");
var ctx = OneContext().context;
if (error is SocketException) {
showServerError(
I18N.of(ctx).connectionRefused,
error.toString()
);
} else if (error is TimeoutException) {
showTimeoutError(ctx);
} else {
showServerError(
I18N.of(ctx).serverError,
error.toString()
);
}
return null;
});
// A null response means something has gone wrong...
if (response == null) {
print("null response from GET ${_url}");
return null;
}
// Check the status code of the response
if (response.statusCode != 200) {
showStatusCodeError(response.statusCode);
return null;
}
// Convert the body of the response to a JSON object
String body = await response.transform(utf8.decoder).join();
try {
var data = json.decode(body);
return data;
} on FormatException {
print("JSON format exception!");
print("${body}");
showServerError(
"Format Exception",
"JSON data format exception:\n${body}"
);
return null;
}
} }
Map<String, String> defaultHeaders() { Map<String, String> defaultHeaders() {
@ -496,6 +549,7 @@ class InvenTreeAPI {
String _authorizationHeader(String username, String password) { String _authorizationHeader(String username, String password) {
if (_token.isNotEmpty) { if (_token.isNotEmpty) {
print("Using TOKEN: ${_token}");
return "Token $_token"; return "Token $_token";
} else { } else {
return "Basic " + base64Encode(utf8.encode('${username}:${password}')); return "Basic " + base64Encode(utf8.encode('${username}:${password}'));

View File

@ -166,39 +166,13 @@ class InvenTreeModel {
*/ */
Future<bool> reload(BuildContext context) async { Future<bool> reload(BuildContext context) async {
var response = await api.get(url, params: defaultGetFilters()) var response = await api.get(url, params: defaultGetFilters());
.timeout(Duration(seconds: 10))
.catchError((e) {
if (e is SocketException) {
showServerError(
I18N.of(context).connectionRefused,
e.toString()
);
}
else if (e is TimeoutException) {
showTimeoutError(context);
} else {
// Re-throw the error
throw e;
}
return null;
});
if (response == null) { if (response == null) {
return false; return false;
} }
if (response.statusCode != 200) { jsondata = response;
showStatusCodeError(response.statusCode);
print("Error retrieving data");
return false;
}
final Map<String, dynamic> data = json.decode(response.body);
jsondata = data;
return true; return true;
} }
@ -265,34 +239,13 @@ class InvenTreeModel {
print("GET: $addr ${params.toString()}"); print("GET: $addr ${params.toString()}");
var response = await api.get(addr, params: params) var response = await api.get(addr, params: params);
.timeout(Duration(seconds: 10))
.catchError((e) {
if (e is SocketException) {
showServerError(I18N.of(context).connectionRefused, e.toString());
}
else if (e is TimeoutException) {
showTimeoutError(context);
} else {
// Re-throw the error
throw e;
}
return null;
});
if (response == null) { if (response == null) {
return null; return null;
} }
if (response.statusCode != 200) { return createFromJson(response);
showStatusCodeError(response.statusCode);
return null;
}
final data = json.decode(response.body);
return createFromJson(data);
} }
Future<InvenTreeModel> create(BuildContext context, Map<String, dynamic> data) async { Future<InvenTreeModel> create(BuildContext context, Map<String, dynamic> data) async {
@ -353,41 +306,21 @@ class InvenTreeModel {
params["limit"] = "${limit}"; params["limit"] = "${limit}";
params["offset"] = "${offset}"; params["offset"] = "${offset}";
var response = await api.get(URL, params: params) var response = await api.get(URL, params: params);
.timeout(Duration(seconds: 10))
.catchError((error) {
if (error is SocketException) {
showServerError(
I18N
.of(OneContext().context)
.connectionRefused,
error.toString()
);
}
return null;
});
if (response == null) { if (response == null) {
return null; return null;
} }
if (response.statusCode != 200) {
showStatusCodeError(response.statusCode);
return null;
}
// Construct the response // Construct the response
InvenTreePageResponse page = new InvenTreePageResponse(); InvenTreePageResponse page = new InvenTreePageResponse();
final data = json.decode(response.body); if (response.containsKey("count") && response.containsKey("results")) {
page.count = response["count"] as int;
if (data.containsKey("count") && data.containsKey("results")) {
page.count = data["count"] as int;
page.results = []; page.results = [];
for (var result in data["results"]) { for (var result in response["results"]) {
page.addResult(createFromJson(result)); page.addResult(createFromJson(result));
} }
@ -417,50 +350,20 @@ class InvenTreeModel {
print("LIST: $URL ${params.toString()}"); print("LIST: $URL ${params.toString()}");
// TODO - Add "timeout" var response = await api.get(URL, params: params);
// TODO - Add error catching
var response = await api.get(URL, params:params)
.timeout(Duration(seconds: 10))
.catchError((e) {
if (e is SocketException) {
showServerError(
I18N.of(context).connectionRefused,
e.toString()
);
}
else if (e is TimeoutException) {
showTimeoutError(context);
} else {
// Re-throw the error
throw e;
}
return null;
});
if (response == null) {
return null;
}
// A list of "InvenTreeModel" items // A list of "InvenTreeModel" items
List<InvenTreeModel> results = new List<InvenTreeModel>(); List<InvenTreeModel> results = new List<InvenTreeModel>();
if (response.statusCode != 200) { if (response == null) {
showStatusCodeError(response.statusCode);
// Return empty list
return results; return results;
} }
final data = json.decode(response.body);
// TODO - handle possible error cases: // TODO - handle possible error cases:
// - No data receieved // - No data receieved
// - Data is not a list of maps // - Data is not a list of maps
for (var d in data) { for (var d in response) {
// Create a new object (of the current class type // Create a new object (of the current class type
InvenTreeModel obj = createFromJson(d); InvenTreeModel obj = createFromJson(d);