Closed qstokkink closed 1 month ago
This is still not short, but I think this is already an improvement:
async getDownloadFiles(infohash: string): Promise<BTFile[]> {
const response = (await this.http.get(`/downloads/${infohash}/files`)).catch(handleHTTPError);
switch (response.status){
case '200':
return response.data.files;
case '404':
return new Array();
default:
// throw an error?
}
}
EDIT: This still may not be doing what I want it to do.
Hold on, according to the docs you can specify per status code whether it should count as an error:
const response = this.http.get(`/downloads/${infohash}/files`,
{ validateStatus: function (status) { return [200, 404].includes(status); } }
).catch(handleHTTPError);
switch (response.status){
case '404':
return new Array();
default:
return response.data.files;
}
I guess we can create a vararg helper method handles(...)
to shorten this further to
const response = this.http.get(`/downloads/${infohash}/files`, handles(200, 404)).catch(handleHTTPError);
@egbertbouman This will be a very invasive change. Are you OK with the proposal of my previous/next post? Or, do you know of other options?
Here's a real patch (in this case the code actually becomes shorter):
diff --git a/src/tribler/ui/src/services/reporting.ts b/src/tribler/ui/src/services/reporting.ts
index 3bd58b6fe..35be66805 100644
--- a/src/tribler/ui/src/services/reporting.ts
+++ b/src/tribler/ui/src/services/reporting.ts
@@ -17,3 +17,7 @@ export function handleHTTPError(error: Error | AxiosError) {
}
return Promise.reject(error);
}
+
+export function handles(...handled: number[]) {
+ return { validateStatus: function (status) { return handled.includes(status); } }
+}
diff --git a/src/tribler/ui/src/services/tribler.service.ts b/src/tribler/ui/src/services/tribler.service.ts
index d5d6ce36e..bd221b025 100644
--- a/src/tribler/ui/src/services/tribler.service.ts
+++ b/src/tribler/ui/src/services/tribler.service.ts
@@ -5,7 +5,7 @@ import { Path } from "@/models/path.model";
import { GuiSettings, Settings } from "@/models/settings.model";
import { Torrent } from "@/models/torrent.model";
import axios, { AxiosError, AxiosInstance } from "axios";
-import { handleHTTPError } from "./reporting";
+import { handleHTTPError, handles } from "./reporting";
const OnError = (event: MessageEvent) => {
@@ -26,7 +26,6 @@ export class TriblerService {
baseURL: this.baseURL,
withCredentials: true,
});
- this.http.interceptors.response.use(function (response) { return response; }, handleHTTPError);
this.events = new EventSource(this.baseURL + '/events', { withCredentials: true });
this.addEventListener("tribler_exception", OnError);
// Gets the GuiSettings
@@ -118,14 +117,7 @@ export class TriblerService {
// Torrents / search
async getMetainfo(uri: string) {
- try {
- return (await this.http.get(`/torrentinfo?uri=${uri}`)).data;
- }
- catch (error) {
- if (axios.isAxiosError(error)) {
- return error.response?.data;
- }
- }
+ return (await this.http.get(`/torrentinfo?uri=${uri}`, handles(200, 400, 500)).catch(handleHTTPError)).data;
}
async getMetainfoFromFile(torrent: File) {
Unless we find something better, I guess this is the most workable solution. I'll start implementing this. It'll take a little while.
I dropped off the proof of concept in the PRs. Let's leave it open for a bit and see if we can think of something nicer.
Important lesson learned from the POC: we HAVE TO handle ALL HTTP status codes that may occur when calling either the triblerservice or the ipv8service.
What was happening in the POC is this:
Essentially, I tried to be lazy and just show the reporter and let the call crash, but letting the call crash means that the entire GUI disappears.
Ok, back to the drawing board. In order to get a vision for the next POC, I'll formulate the problem more concretely now. Let's see if we can work off of the pattern of getMetainfo
.
The above is not yet complete. There are three different types of errors that should be handled, and one error that we should crash on:
AxiosError
with an expected non-200 HTTP status. This CAN also be a 500 status (see next point).AxiosError
with an unexpected 500 HTTP status.AxiosError
with a raw network error.We could also get a base Error
but that should not be fixed in the handler - so I suggest just throwing that again and crashing.
The current implementation of getMetainfo
implements points 1 and 4. So, the concrete problem is this:
Documenting more off-GitHub discussions.
As pointed out by @egbertbouman, error responses that are not handled have an additional key in the response dictionary: handled: false
. With this info we can distinguish points 1 and 2.
Regarding point 3, there are 12 non-user errors that may occur, see: https://github.com/axios/axios?tab=readme-ov-file#error-types Explicitly handling these would require a construction like the following:
if (error.code === 'ERR_NETWORK') { ...
} else if (error.code === 'ERR_CANCELED') { ...
} ... etc
However, the documentation also suggests that simply checking if (error.response)
is sufficient to simply group up all of the point-3 type errors. For our purposes that should be sufficient.
With that, I think we have all the errors covered that can occur inside of the service handlers. It would look something (untested) like the following:
async getMetainfo(uri: string) {
try {
return (await this.http.get(`/torrentinfo?uri=${uri}`)).data;
}
catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) return "RAW_AXIOS_ERROR";
if (error.response?.data?.error?.handled && error.response.data.error.handled == false) handleHTTPError(error);
return error.response?.data;
}
throw error;
}
}
Now, there is still the matter of creating a response for the caller of the service. Currently, every caller of such a service function would require three checks (edit: in only two blocks):
Perhaps it makes more sense to NOT return the error dictionary to the caller of a service function when handled == false
. The callers can probably not do anything useful with this info anyway and this allows them to lump in the "raw AxiosError or an unhandeld error" with a single check.
Given all of the above, I'm thinking the following might be a convenient signature:
service.method(): Promise< HAPPY_FLOW_TYPE | undefined > { ... }
This allows the typing system to detect callers of this function that do not handle undefined
. So, this would ensure that unexpected errors are handled.
Memo (for myself): there is yet another type of error that could come from the core:
return RESTResponse(body=b"This is not JSON, oh no!")
Alright, the failsafe has been merged in #8150
To properly close this issue, we still need a PR that looks at all the REST responses and gracefully handles them in the GUI.
In #8120, I introduced an error reporter that uses an Axios
interceptor
. The intent was that this would only intercept unhandled errors.What actually happens is that this interceptor is called BEFORE any handlers. This means that every single non-2xx HTTP code is reported as an error, regardless of whether it is handled later or not.
Regrettably, the implication here is that we have to attach the error handler manually to every single request in the Tribler + IPv8 services. For example
.. turns into something like ..
This will roughly quadruple the file sizes of the services. It would be nice if there was some way to get this shorter.