-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
176 additions
and
17 deletions.
There are no files selected for viewing
193 changes: 176 additions & 17 deletions
193
...ervice/src/main/java/com/calpano/graphinout/apprestservice/rest-api-design.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,204 @@ | ||
= REST API | ||
:toc: | ||
|
||
== Requirements | ||
|
||
- Let user upload a single file | ||
- Let user download converted file as GraphML | ||
|
||
== Requirement | ||
.Chaining | ||
Given a URL like `http://example.org/mygraph.dot`, | ||
construct a URL like | ||
`http://graphinout.com/api/convert?format=graphml&url=http://example.org/mygraph.dot` => Response is a GraphML file | ||
|
||
== Design | ||
.Usage for a single file | ||
. User uploads file -> gets redirected to /session/<inputId> | ||
. User downloads, e.g. /session/<inputId>/log.txt or /session/<inputId>/result.graphml.xml | ||
|
||
== Variant: ZIP | ||
- Request: POST file | ||
- Response: response.zip (log.txt, a.graphml.xml) | ||
|
||
== Variant: Resources <-- | ||
.Reasons | ||
* + Better timeout-resilience | ||
* + Better concurrency (impl) | ||
* - Several requests required | ||
* + Better debuggability | ||
|
||
- Request: POST file + optional sessionId | ||
- Response: Redirect to /session/<inputId> | ||
=== Endpoint: `/api` | ||
==== POST with MultiPart | ||
- Request: POST file + optional inputId | ||
- Response: HTTP 308 Permanent Redirect to /session/<inputId> | ||
- Errors: | ||
** inputId is too lange -> HTTP 413 Request Entity Too Large | ||
** at least onf of the given files is empty -> HTTP 400 Bad Request | ||
** at least onf of the given files is too large -> HTTP 413 Payload Too Large | ||
** Server fails to store (one of) the files -> HTTP 500 Internal Server Error | ||
|
||
PostCondition: All required files have been saved to directory. | ||
|
||
NOTE: Keep sessions for 2 weeks? | ||
|
||
=== Endpoint `/api/session/<inputId>` | ||
==== GET | ||
- Request: GET /session/<inputId> | ||
- Response: "still pending" or "done" (JSON or XML response) | ||
** Response: Show links (JSON or XML response) to /session/<inputId>/log.txt and /session/<inputId>/a.graphml.xml -- can download from there | ||
- Response as JSON: | ||
* "still pending" or | ||
* "failed" if there was an error processing the file(s), include links to | ||
** `/api/sessions/<inputId>/output.log` | ||
* "done" includes links: | ||
** `/api/sessions/<inputId>/output.log` | ||
** `/api/sessions/<inputId>/output.graph-xml` | ||
** `/api/sessions/<inputId>/all.zip` | ||
|
||
Keep sessions for 2 weeks? | ||
- Load `status.json` from directory and use it for status? | ||
|
||
- Request: GET /session/<inputId>/all.zip | ||
- Response: ZIP file with log.txt and a.graphml.xml | ||
- Errors: | ||
** inputId is invalid/directory does not exists -> HTTP 404 Not Found | ||
** I/O while reading files -> HTTP 500 Internal Server Error | ||
|
||
=== Session / InputIds? | ||
.Both | ||
- UUID v7? | ||
- User-supplied | ||
CAUTION: Don't offer download of original files -- otherwise we are a file sharing site! | ||
|
||
==== POST with MultiPart | ||
- Delete existing input and result files from directory | ||
- Upload new files | ||
- Return HTTP 200 OK (not 201 Created, because resource DID already exist) | ||
|
||
- Errors: | ||
** at least onf of the given files is empty -> HTTP 400 Bad Request | ||
** at least onf of the given files is too large -> HTTP 413 Payload Too Large | ||
** Server fails to store (one of) the files -> HTTP 500 Internal Server Error | ||
|
||
|
||
=== Endpoint `/api/session/<inputId>/output.log` | ||
==== GET | ||
Return file at `/configured-base-dir/encoded(<inputId>)/output.log` | ||
|
||
- Errors: | ||
** inputId is invalid/directory does not exists -> HTTP 404 Not Found | ||
** processing not yet done -> HTTP 404 Not Found | ||
|
||
|
||
=== Endpoint `/api/session/<inputId>/output.graphml.xml` | ||
==== GET | ||
Return file at `/configured-base-dir/encoded(<inputId>)/output.graphml.xml` | ||
|
||
=== Endpoint `/api/session/<inputId>/all.zip` | ||
==== GET | ||
- Generate a streaming ZIP file containing both output files | ||
|
||
|
||
=== Session / InputIds? | ||
.Both | ||
- UUID v7? | ||
- User-supplied | ||
|
||
== File Management | ||
|
||
.Save | ||
Two variants | ||
User supplies us with a inputId:: base64 encode userId as valid directory name | ||
We generate a inputId:: UUID | ||
|
||
NOTE: Storing a new file at an existing inputId overwrite the existing file AND must delete existing result files. | ||
|
||
TIP: Use (encoded) inputId as directory name (I.e. use only `[0-9a-zA-Z-=]`) | ||
|
||
.Load | ||
Encode inputId with base64, look in directory. | ||
|
||
.Encode Algorithm | ||
---- | ||
IF input is a valid directory name | ||
RETURN it | ||
ELSE RETURN base64encode it | ||
---- | ||
|
||
.Encode example: | ||
Input:: `http://graphdrawing.de/contest2018/got-graph.graphml` | ||
Encoded:: `aHR0cDovL2dyYXBoZHJhd2luZy5kZS9jb250ZXN0MjAxOC9nb3QtZ3JhcGguZ3JhcGhtbA==` | ||
|
||
.Directory Mapping | ||
[options="header"] | ||
|=== | ||
| File type | File name | ||
| Input files | (use original file name with extensions) | ||
| Log file | `output.log` | ||
| GraphML result file | `output.graphml.xml` | ||
| Status file | `status.json` | ||
|=== | ||
|
||
.Our Directory | ||
[source] | ||
---- | ||
/configured-base-dir | ||
/3a5a4b40-fe30-4696-8e90-73cd16dac1f6 <1> | ||
/my-graph.dot | ||
/output.log | ||
/output.graphml.xml | ||
/my-input-id <2> | ||
/MY-LARGE-grAph.tgf | ||
/output.log | ||
/output.graphml.xml | ||
/aHR0cDovL2dyYXBoZHJhd2luZy5kZS9jb250ZXN0MjAxOC9nb3QtZ3JhcGguZ3JhcGhtbA== <3> | ||
/got-graph.graphml | ||
/output.log | ||
/output.graphml.xml | ||
---- | ||
<1> user had no inputId, we generated a UUIDv4 | ||
<2> user supplied `my-input-id` as inputId | ||
<3> user supplied a URL as inputId, we encoded it | ||
|
||
.Export as ZIP | ||
- Include only log file and graphML result file | ||
- Create ZIPs on the fly, don't store them | ||
|
||
=== What should be in `output.log` ? | ||
- ContentErrors | ||
- (Logger output? If possible?) | ||
|
||
|
||
== Concurrency | ||
What happens if | ||
|
||
- user uploads (larger) graph input | ||
- user requests /session/<inputId>/output.log while processing | ||
** (1) did not start, | ||
** (2) is running, | ||
** (3) is done | ||
- user requests /session/<inputId>/output.result.graph while processing | ||
** (1) did not start, | ||
** (2) is running, | ||
** (3) is done | ||
- user requests /session/<inputId>/all.zip while processing | ||
** (1) did not start, | ||
** (2) is running, | ||
** (3) is done | ||
|
||
== Metadata management | ||
.`status.json` | ||
- For each task step: | ||
** timestamp | ||
** Caller IP address | ||
** file size | ||
** HTTP result | ||
|
||
- Current status: | ||
** "initial" -- directory prepared, no files yet | ||
** "uploading" -- files are being uploaded | ||
** "processing" -- files are being processed | ||
** "success" | ||
** "failed" | ||
|
||
.States | ||
[plantuml] | ||
.... | ||
state initial | ||
[*] --> initial | ||
initial -> uploading : POST /api | ||
uploading -> processing | ||
uploading --> failed : upload fails | ||
processing --> success | ||
processing --> failed | ||
success --> done | ||
failed --> done | ||
done --> uploading : POST /<inputId> | ||
done --> [*] | ||
.... |