mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +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:
parent
d6e9746d8c
commit
71340da068
226
lib/api.dart
226
lib/api.dart
@ -165,44 +165,19 @@ class InvenTreeAPI {
|
||||
|
||||
print("Connecting to ${apiUrl} -> ${username}:${password}");
|
||||
|
||||
var response = await get("").timeout(Duration(seconds: 10)).catchError((error) {
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
// Request the /api/ endpoint - response is a json object
|
||||
var response = await get("");
|
||||
|
||||
// Null response means something went horribly wrong!
|
||||
if (response == null) {
|
||||
// Null (or error) response: Show dialog and exit
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
// Any status code other than 200!
|
||||
|
||||
showStatusCodeError(response.statusCode);
|
||||
|
||||
// TODO: Interpret the error codes and show custom message?
|
||||
return false;
|
||||
}
|
||||
|
||||
var data = json.decode(response.body);
|
||||
|
||||
print("Response from server: $data");
|
||||
print("Response from server: ${response}");
|
||||
|
||||
// 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(
|
||||
"Missing Data",
|
||||
@ -213,11 +188,11 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
// Record server information
|
||||
_version = data["version"];
|
||||
instance = data['instance'] ?? '';
|
||||
_version = response["version"];
|
||||
instance = response['instance'] ?? '';
|
||||
|
||||
// Default API version is 1 if not provided
|
||||
_apiVersion = data['apiVersion'] as int ?? 1;
|
||||
_apiVersion = response['apiVersion'] as int ?? 1;
|
||||
|
||||
if (_apiVersion < _minApiVersion) {
|
||||
|
||||
@ -245,14 +220,7 @@ class InvenTreeAPI {
|
||||
|
||||
print("Requesting token from server");
|
||||
|
||||
response = await get(_URL_GET_TOKEN).timeout(Duration(seconds: 10)).catchError((error) {
|
||||
|
||||
print("Error requesting token:");
|
||||
print(error);
|
||||
|
||||
response = null;
|
||||
|
||||
});
|
||||
response = await get(_URL_GET_TOKEN);
|
||||
|
||||
if (response == null) {
|
||||
showServerError(
|
||||
@ -263,31 +231,25 @@ class InvenTreeAPI {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
return false;
|
||||
} else {
|
||||
var data = json.decode(response.body);
|
||||
|
||||
if (!data.containsKey("token")) {
|
||||
showServerError(
|
||||
if (!response.containsKey("token")) {
|
||||
showServerError(
|
||||
I18N.of(OneContext().context).tokenMissing,
|
||||
"Access token missing from response"
|
||||
);
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the received token
|
||||
_token = data["token"];
|
||||
print("Received token - $_token");
|
||||
// Return the received token
|
||||
_token = response["token"];
|
||||
print("Received token - $_token");
|
||||
|
||||
// Request user role information
|
||||
await getUserRoles();
|
||||
// Request user role information
|
||||
await getUserRoles();
|
||||
|
||||
// Ok, probably pretty good...
|
||||
return true;
|
||||
|
||||
// Ok, probably pretty good...
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
bool disconnectFromServer() {
|
||||
@ -348,32 +310,19 @@ class InvenTreeAPI {
|
||||
// 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!
|
||||
// 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
|
||||
|
||||
try {
|
||||
var data = json.decode(response.body);
|
||||
|
||||
if (data.containsKey('roles')) {
|
||||
// Save a local copy of the user roles
|
||||
roles = data['roles'];
|
||||
}
|
||||
}
|
||||
on FormatException {
|
||||
// Old server has re-directed away from the API
|
||||
}
|
||||
} else {
|
||||
}
|
||||
});
|
||||
if (response.containsKey('roles')) {
|
||||
// Save a local copy of the user roles
|
||||
roles = response['roles'];
|
||||
}
|
||||
}
|
||||
|
||||
bool checkPermission(String role, String permission) {
|
||||
@ -454,11 +403,42 @@ class InvenTreeAPI {
|
||||
);
|
||||
}
|
||||
|
||||
// Perform a GET request
|
||||
Future<http.Response> get(String url, {Map<String, String> params}) async {
|
||||
HttpClient _client(bool allowBadCert) {
|
||||
|
||||
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 _headers = defaultHeaders();
|
||||
|
||||
print("GET: ${_url}");
|
||||
|
||||
// If query parameters are supplied, form a query string
|
||||
if (params != null && params.isNotEmpty) {
|
||||
String query = '?';
|
||||
@ -475,7 +455,80 @@ class InvenTreeAPI {
|
||||
|
||||
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() {
|
||||
@ -496,6 +549,7 @@ class InvenTreeAPI {
|
||||
|
||||
String _authorizationHeader(String username, String password) {
|
||||
if (_token.isNotEmpty) {
|
||||
print("Using TOKEN: ${_token}");
|
||||
return "Token $_token";
|
||||
} else {
|
||||
return "Basic " + base64Encode(utf8.encode('${username}:${password}'));
|
||||
|
@ -166,39 +166,13 @@ class InvenTreeModel {
|
||||
*/
|
||||
Future<bool> reload(BuildContext context) async {
|
||||
|
||||
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;
|
||||
});
|
||||
var response = await api.get(url, params: defaultGetFilters());
|
||||
|
||||
if (response == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
print("Error retrieving data");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> data = json.decode(response.body);
|
||||
|
||||
jsondata = data;
|
||||
jsondata = response;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -265,34 +239,13 @@ class InvenTreeModel {
|
||||
|
||||
print("GET: $addr ${params.toString()}");
|
||||
|
||||
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;
|
||||
});
|
||||
var response = await api.get(addr, params: params);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
final data = json.decode(response.body);
|
||||
|
||||
return createFromJson(data);
|
||||
return createFromJson(response);
|
||||
}
|
||||
|
||||
Future<InvenTreeModel> create(BuildContext context, Map<String, dynamic> data) async {
|
||||
@ -353,41 +306,21 @@ class InvenTreeModel {
|
||||
params["limit"] = "${limit}";
|
||||
params["offset"] = "${offset}";
|
||||
|
||||
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;
|
||||
});
|
||||
var response = await api.get(URL, params: params);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Construct the response
|
||||
InvenTreePageResponse page = new InvenTreePageResponse();
|
||||
|
||||
final data = json.decode(response.body);
|
||||
|
||||
if (data.containsKey("count") && data.containsKey("results")) {
|
||||
page.count = data["count"] as int;
|
||||
if (response.containsKey("count") && response.containsKey("results")) {
|
||||
page.count = response["count"] as int;
|
||||
|
||||
page.results = [];
|
||||
|
||||
for (var result in data["results"]) {
|
||||
for (var result in response["results"]) {
|
||||
page.addResult(createFromJson(result));
|
||||
}
|
||||
|
||||
@ -417,50 +350,20 @@ class InvenTreeModel {
|
||||
|
||||
print("LIST: $URL ${params.toString()}");
|
||||
|
||||
// TODO - Add "timeout"
|
||||
// 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;
|
||||
}
|
||||
var response = await api.get(URL, params: params);
|
||||
|
||||
// A list of "InvenTreeModel" items
|
||||
List<InvenTreeModel> results = new List<InvenTreeModel>();
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
|
||||
// Return empty list
|
||||
if (response == null) {
|
||||
return results;
|
||||
}
|
||||
|
||||
final data = json.decode(response.body);
|
||||
|
||||
// TODO - handle possible error cases:
|
||||
// - No data receieved
|
||||
// - 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
|
||||
InvenTreeModel obj = createFromJson(d);
|
||||
|
Loading…
x
Reference in New Issue
Block a user