[DEV] well support of the callback for stream

This commit is contained in:
Edouard DUPIN 2024-04-17 12:12:50 +02:00
parent 9cd71ab601
commit 7674d9a299
3 changed files with 87 additions and 57 deletions

View File

@ -921,7 +921,9 @@ public class DataAccess {
} }
// ps.execute(); // ps.execute();
} catch (final SQLException ex) { } catch (final SQLException ex) {
LOGGER.error("Fail SQL request: {}", ex.getMessage());
ex.printStackTrace(); ex.printStackTrace();
throw new DataAccessException("Fail to Insert data in DB : " + ex.getMessage());
} finally { } finally {
entry.close(); entry.close();
} }

View File

@ -57,7 +57,7 @@ public class DataFactoryTsApi {
/** /**
* API of the server (auto-generated code) * API of the server (auto-generated code)
*/ */
import { HTTPMimeType, HTTPRequestModel, ModelResponseHttp, RESTConfig, ProgressCallback, RESTRequestJson, RESTRequestJsonArray, RESTRequestVoid } from "./rest-tools" import { HTTPMimeType, HTTPRequestModel, ModelResponseHttp, RESTConfig, RESTCallbacks, RESTRequestJson, RESTRequestJsonArray, RESTRequestVoid } from "./rest-tools"
import { """; import { """;
for (final Class<?> clazz : classs) { for (final Class<?> clazz : classs) {
@ -398,7 +398,7 @@ public class DataFactoryTsApi {
builder.append(" data,"); builder.append(" data,");
} }
if (needGenerateProgress) { if (needGenerateProgress) {
builder.append(" progress,"); builder.append(" callback,");
} }
builder.append(" }: {"); builder.append(" }: {");
builder.append("\n\t\trestConfig: RESTConfig,"); builder.append("\n\t\trestConfig: RESTConfig,");
@ -468,7 +468,7 @@ public class DataFactoryTsApi {
builder.append(","); builder.append(",");
} }
if (needGenerateProgress) { if (needGenerateProgress) {
builder.append("\n\t\tprogress?: ProgressCallback,"); builder.append("\n\t\tcallback?: RESTCallbacks,");
} }
builder.append("\n\t}): Promise<"); builder.append("\n\t}): Promise<");
builder.append(tmpReturn.tsTypeName); builder.append(tmpReturn.tsTypeName);
@ -536,7 +536,7 @@ public class DataFactoryTsApi {
builder.append("\n\t\t\tdata,"); builder.append("\n\t\t\tdata,");
} }
if (needGenerateProgress) { if (needGenerateProgress) {
builder.append("\n\t\t\tprogress,"); builder.append("\n\t\t\tcallback,");
} }
builder.append("\n\t\t}"); builder.append("\n\t\t}");
if (tmpReturn.tsCheckType != null) { if (tmpReturn.tsCheckType != null) {

View File

@ -41,7 +41,7 @@ export interface RESTModel {
accept?: HTTPMimeType; accept?: HTTPMimeType;
// Content of the local data. // Content of the local data.
contentType?: HTTPMimeType; contentType?: HTTPMimeType;
// Mode of the TOKEN in urk or Header // Mode of the TOKEN in URL or Header (?token:${tokenInUrl})
tokenInUrl?: boolean; tokenInUrl?: boolean;
} }
@ -71,13 +71,22 @@ function isNullOrUndefined(data: any): data is undefined | null {
return data === undefined || data === null; return data === undefined || data === null;
} }
export type RESTRequestType = { // generic progression callback
export type ProgressCallback = (count: number, total: number) => void;
// Rest generic callback have a basic model to upload and download advancement.
export interface RESTCallbacks {
progressUpload?: ProgressCallback,
progressDownload?: ProgressCallback
};
export interface RESTRequestType {
restModel: RESTModel, restModel: RESTModel,
restConfig: RESTConfig, restConfig: RESTConfig,
data?: any, data?: any,
params?: object, params?: object,
queries?: object, queries?: object,
progress?: ProgressCallback, callback?: RESTCallbacks,
}; };
function removeTrailingSlashes(input: string): string { function removeTrailingSlashes(input: string): string {
@ -124,54 +133,72 @@ export function RESTUrl({ restModel, restConfig, params, queries }: RESTRequestT
} }
export type ProgressCallback = (count: number, total: number) => void; export function fetchProgress(generateUrl: string, { method, headers, body }: {
// input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
export function fetchProgress<TYPE>(generateUrl: string, { method, headers, body }: {
method: HTTPRequestModel, method: HTTPRequestModel,
headers: any, headers: any,
body: any, body: any,
}, progress: ProgressCallback): Promise<Response> { }, { progressUpload, progressDownload }: RESTCallbacks): Promise<Response> {
//async function fetchForm(form, options = {}) {
const action = generateUrl;
const data = body;
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
xhr.responseType = 'blob'; // Stream the upload progress
xhr.onreadystatechange = () => { if (progressUpload) {
xhr.upload.addEventListener("progress", (dataEvent) => {
if (dataEvent.lengthComputable) {
//console.log(` ==> has a progress event: ${dataEvent.loaded} / ${dataEvent.total}`);
progressUpload(dataEvent.loaded, dataEvent.total);
}
});
}
// Stream the download progress
if (progressDownload) {
xhr.addEventListener("progress", (dataEvent) => {
if (dataEvent.lengthComputable) {
//console.log(` ==> download progress:: ${dataEvent.loaded} / ${dataEvent.total}`);
progressUpload(dataEvent.loaded, dataEvent.total);
}
});
}
// Check if we have an internal Fail:
xhr.addEventListener('error', () => {
reject(new TypeError('Failed to fetch'))
});
// Capture the end of the stream
xhr.addEventListener("loadend", () => {
if (xhr.readyState != 4) { if (xhr.readyState != 4) {
// done console.log(` ==> READY state`);
return; return;
} }
// Stream is ended, transform in a generic response:
const response = new Response(xhr.response, { const response = new Response(xhr.response, {
status: xhr.status, status: xhr.status,
statusText: xhr.statusText statusText: xhr.statusText
}); });
resolve(response); const headersArray = xhr.getAllResponseHeaders().trim().replaceAll("\r\n", "\n").split('\n');
} headersArray.forEach(function (header) {
// If fail: const firstColonIndex = header.indexOf(':');
xhr.addEventListener('error', () => { if (firstColonIndex !== -1) {
reject(new TypeError('Failed to fetch')) var key = header.substring(0, firstColonIndex).trim();
}); var value = header.substring(firstColonIndex + 1).trim();
// Link the progression callback response.headers.set(key, value);
if (progress) { } else {
xhr.addEventListener('progress', (dataEvent) => { response.headers.set(header, "");
progress(dataEvent.loaded, dataEvent.total); }
}); });
} resolve(response);
// open the socket });
xhr.open(method, action, true); xhr.open(method, generateUrl, true);
// configure the header
if (!isNullOrUndefined(headers)) { if (!isNullOrUndefined(headers)) {
for (const [key, value] of Object.entries(headers)) { for (const [key, value] of Object.entries(headers)) {
xhr.setRequestHeader(key, value as string); xhr.setRequestHeader(key, value as string);
} }
} }
xhr.send(data); console.log(` ==> send`);
xhr.send(body);
console.log(` ==> send done`);
}); });
} }
export function RESTRequest({ restModel, restConfig, data, params, queries, progress }: RESTRequestType): Promise<ModelResponseHttp> { export function RESTRequest({ restModel, restConfig, data, params, queries, callback }: RESTRequestType): Promise<ModelResponseHttp> {
// Create the URL PATH: // Create the URL PATH:
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries });
let headers: any = {}; let headers: any = {};
@ -198,33 +225,34 @@ export function RESTRequest({ restModel, restConfig, data, params, queries, prog
} }
body = formData body = formData
} }
console.log(`Call ${generateUrl}`)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let action: Promise<Response> = undefined; let action: undefined | Promise<Response> = undefined;
if (isNullOrUndefined(progress)) { if (isNullOrUndefined(callback) || (isNullOrUndefined(callback.progressDownload) && isNullOrUndefined(callback.progressUpload))) {
// No information needed: call the generic fetch interface
action = fetch(generateUrl, { action = fetch(generateUrl, {
method: restModel.requestType, method: restModel.requestType,
headers, headers,
body, body,
}); });
} else { } else {
// need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x
action = fetchProgress(generateUrl, { action = fetchProgress(generateUrl, {
method: restModel.requestType, method: restModel.requestType ?? HTTPRequestModel.GET,
headers, headers,
body, body,
}, progress); }, callback);
} }
action.then((response: Response) => { action.then((response: Response) => {
if (response.status >= 200 && response.status <= 299) { if (response.status >= 200 && response.status <= 299) {
const contentType = response.headers.get('Content-Type'); const contentType = response.headers.get('Content-Type');
if (restModel.accept !== contentType) { if (restModel.accept !== contentType) {
reject({ reject({
name: `REST check wrong type: ${restModel.accept} != ${contentType}`,
message: "rest-tools.ts Wrong type in the message return type",
time: Date().toString(), time: Date().toString(),
status: 901, status: 901,
error: `REST check wrong type: ${restModel.accept} != ${contentType}`,
statusMessage: "Fetch error", statusMessage: "Fetch error",
}); message: "rest-tools.ts Wrong type in the message return type"
} as RestErrorResponse);
} else if (contentType === HTTPMimeType.JSON) { } else if (contentType === HTTPMimeType.JSON) {
response response
.json() .json()
@ -234,32 +262,32 @@ export function RESTRequest({ restModel, restConfig, data, params, queries, prog
}) })
.catch((reason: any) => { .catch((reason: any) => {
reject({ reject({
name: `REST parse json fail: ${reason}`,
message: "rest-tools.ts Wrong message model to parse",
time: Date().toString(), time: Date().toString(),
status: 902, status: 902,
error: `REST parse json fail: ${reason}`,
statusMessage: "Fetch parse error", statusMessage: "Fetch parse error",
}); message: "rest-tools.ts Wrong message model to parse"
} as RestErrorResponse);
}); });
} else { } else {
resolve({ status: response.status, data: response.body }); resolve({ status: response.status, data: response.body });
} }
} else { } else {
reject({ reject({
name: `${response.body}`,
message: "rest-tools.ts Wrong return code",
time: Date().toString(), time: Date().toString(),
status: response.status, status: response.status,
error: `${response.body}`,
statusMessage: "Fetch code error", statusMessage: "Fetch code error",
}); message: "rest-tools.ts Wrong return code"
} as RestErrorResponse);
} }
}).catch((error: any) => { }).catch((error: any) => {
reject({ reject({
name: error,
message: "http-wrapper.ts detect an error in the fetch request",
time: Date(), time: Date(),
status: 999, status: 999,
statusMessage: "Fetch catch error" error: error,
statusMessage: "Fetch catch error",
message: "http-wrapper.ts detect an error in the fetch request"
}); });
}); });
}); });
@ -274,12 +302,12 @@ export function RESTRequestJson<TYPE>(request: RESTRequestType, checker: (data:
resolve(value.data); resolve(value.data);
} else { } else {
reject({ reject({
name: "REST Fail to verify the data",
message: "api.ts Check type as fail",
time: Date().toString(), time: Date().toString(),
status: 950, status: 950,
error: "REST Fail to verify the data",
statusMessage: "API cast ERROR", statusMessage: "API cast ERROR",
}); message: "api.ts Check type as fail"
} as RestErrorResponse);
} }
}).catch((reason: RestErrorResponse) => { }).catch((reason: RestErrorResponse) => {
reject(reason); reject(reason);
@ -293,12 +321,12 @@ export function RESTRequestJsonArray<TYPE>(request: RESTRequestType, checker: (d
resolve(value.data); resolve(value.data);
} else { } else {
reject({ reject({
name: "REST Fail to verify the data",
message: "api.ts Check type as fail",
time: Date().toString(), time: Date().toString(),
status: 950, status: 950,
error: "REST Fail to verify the data",
statusMessage: "API cast ERROR", statusMessage: "API cast ERROR",
}); message: "api.ts Check type as fail"
} as RestErrorResponse);
} }
}).catch((reason: RestErrorResponse) => { }).catch((reason: RestErrorResponse) => {
reject(reason); reject(reason);