Local Webserver

From ScummVM :: Wiki
Jump to navigation Jump to search

Overview

AJAX Files Manager

The Cloud PR also brings a Wi-Fi Sharing feature. As mentioned in the cloud storage support description, webserver is used for Storage connect, but Wi-Fi Sharing uses it much more.

In short, it allows one to access files via network - upload and download files from the device ScummVM is working on. For example, one can upload a game from a PC onto a smartphone without USB cable. This is an alternative to the external cloud usage.

Wi-Fi Sharing

Server options in Cloud tab

ScummVM now can start a local webserver (using SDL_Net), which provides the Files Manager: users can navigate through directories, download files, create new directories or upload files using the browser on another device. Information about the server is available on the new Options dialog's Cloud tab. Users could launch the webserver there or change the port it's using.

While launched, it's available on "http://localhost:12345/" (by default to the moment) through any browser. ScummVM tries to detect local network IP address, so users could see that in Cloud tab and enter on any device within their local network.

It provides two Files Manager implementations: on "/files" and on "/filesAJAX". The latter is used by default and does GET requests to "/list" to list directories and navigate through directories without page refreshing, while "/files" use "path" GET parameter to specify which page should be shown.

User can download a file ("/download"), create a directory ("/create") or upload files ("/upload"). Multiple files could be uploaded. User can also select a directory to upload (works in Chrome only).

Two "virtual" directories available: "/root/" and "/saves/". The first is filesystem root ("/" on Unix and similar to "My Computer" on Windows) and the other is shortcut to savespath.

Endpoints

GET /
redirects to /filesAJAX

GET /?code=
used by Cloud, remembers the code to be fetched by Storage

GET /files[?path=]
lists the directory with specified path
if no path specified, "/root/" and "/saves/" are shown

GET /filesAJAX[?path=]
lists the directories
if there is path specified, it's the one it starts from

GET /download?path=
streams the specified file

GET /create?path=&directory_name=[&ajax=]
creates the directory with name directory_name within path (which must exist)
if ajax is "true", returns success/error message as JSON object

GET /list?path=
returns JSON with directory information

POST /upload?path=[&ajax=]
(path and ajax are GET parameters)
does POST form/multipart upload
supports multiple files
searches for a file attachment within upload_file-f and upload_file-d POST form/multipart parameters and uploads these into path directory
if ajax is "true", user is redirected to /filesAJAX instead of /files after the upload

GET /*
if the specified path is available with wwwroot.zip archive, streams the file specified
files starting with '.' are not streamed
for example, "/icons/dir.png" would stream the png file and "/.index.html" won't stream the html

Resources

Webserver's resources are stored in a wwwroot.zip archive. If it's not available, server responds with the less stylish, yet working pages. /filesAJAX won't work though. One can use make_archive.py script in backends/networking/ to rebuild the archive. As already mentioned, files starting with '.' are not streamed from the archive, the others are easily available with GET /path/to/file requests.

How LocalWebserver works

LocalWebserver is the central class, which represents the server itself. It storages the accepting (server) socket, Client objects and BaseHandlers.

In LocalWebserver's constructor all BaseHandlers are set. Callbacks to their handling methods are storaged in the HashMap with String keys. Keys specify which path is handled by this method. If no handler found for path, _defaultHandler is used.

When server socket accepts a connection, it creates a new Client object, where corresponding socket would be stored. Client is created in READING_HEADERS state. In that state it should be reading bytes from the socket until HTTP headers are completely received (or until it can say for sure that it's not a valid HTTP request).

When it changes state to READ_HEADERS, LocalWebserver tries to find a handler for this Client. It's either an exact match (for example, "/filesAJAX") or a default handler (e.g. for "/style.css"). BaseHandlers are the ones which try to understand user's request and compile a response if that's possible. If no response is set for the Client, it gets a default "INVALID REQUEST" response from LocalWebserver.

Client has all headers and also URL parsed, so BaseHandlers could access method, path, query parameters information. Based on these response is compiled. ClientHandler classes used to send that response. Most used one is GetClientHandler, which just sends the response (possibly with redirect headers). When Client gets a ClientHandler set, it turns into BEING_HANDLED state. In this state it just calls handler's handle() method. ClientHandler uses Client's methods to send/recv some information from Client's socket. When done, it calls close(), so socket closes, memory frees and Client gets INVALID.

In case of POST form/multipart request Client is handled by UploadFileClientHandler, which searches for files within Client's request content and saves those. When done, it replaces itself with usual GetClientHandler to redirect user back to Files Manager page.

To parse the request the right way, Client uses a Reader, which could handle reading HTTP GET or POST form/multipart request. It keeps the right state no matter how small the chunks of request are: one can pass the request byte by byte or in buffers of limited size, but Reader won't miss headers end or a boundary.

See also: information about LocalWebserver/Client/Reader data flow.