1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * HTTP Server Module
 * Features:
 * - Supports GET/POST requests
 * - Supports file upload via raw body ('application/octet-stream') and multipart form data ('multipart/form-data').
 * - Supports file download
 * - Supports static html file service
 * - Multi-threading not supported, all functions should run in a single thread. For cross-thread communication, use dxEventbus to pass data
 * 
 * Usage:
 * - Simple WebServer
 * - Simple Web API Service
 * 
 * Doc/Demo : https://github.com/DejaOS/DejaOS
 */
import { httpserverClass } from './libvbar-m-dxhttpserver.so'
 
let server = null;
const httpserver = {}
httpserver.init = function () {
    if (!server) {
        server = new httpserverClass();
    }
}
/**
 * Route the HTTP request. A maximum of 100 routes can be registered.
 * @param {string} path - The path to route the request. Should start with '/'. 
 *                        Supports wildcard matching by ending with '/*'.
 * @param {function} callback - The callback function to handle the request
 * @param {object} callback.req - The request object
 * @param {string} callback.req.method - The HTTP method (GET, POST, etc.)
 * @param {string} callback.req.url - The request URL
 * @param {string} callback.req.query - The query string (e.g. "a=1&b=2")
 * @param {object} callback.req.headers - The request headers
 * @param {string} [callback.req.body] - The request body (only for specific Content-Type)
 * @param {function(string): boolean} callback.req.saveFile - Function to save uploaded file from raw request body. Returns true on success.
 * @param {function(string): object} callback.req.saveMultipartFile - Function to handle 'multipart/form-data' upload. It saves the file part to the specified path and returns other fields as an object.
 * @param {object} callback.res - The response object
 * @param {function} callback.res.send - Send response with body and headers,the header should be a object and the size should be < 512
 * @param {function} callback.res.sendFile - Send file as response
 * 
 * @example
 * // Basic usage 
 * httpserver.route('/hello', function(req, res) {
 *   res.send('Hello World', {'Content-Type': 'text/plain'});
 * });
 * 
 * @example
 * // Wildcard route to handle all requests under /api/
 * httpserver.route('/api/*', function(req, res) {
 *   // req.url will be the full URL, e.g., "/api/users/123"
 *   if (req.url.startsWith('/api/users/')) {
 *     const userId = req.url.substring(11);
 *     res.send(`User ID is ${userId}`);
 *   } else {
 *     res.send('Welcome to the API!');
 *   }
 * });
 * 
 * @example
 * // Handle file upload (raw body). Client should POST the file content directly.
 * // Example with curl:
 * // curl -X POST --data-binary "@/path/to/your/file.txt" \
 * //   -H "Content-Type: application/octet-stream" \
 * //   http://127.0.0.1:8080/upload
 * httpserver.route('/upload', function(req, res) {
 *   req.saveFile('/app/code/data/file_saved.txt');
 *   res.send('File saved');
 * });
 * 
 * @example
 * // Handle multipart/form-data upload.
 * // This saves the file part to the specified path and returns other form fields.
 * // Example with curl:
 * // curl -X POST -F "file1=@/path/to/your/file.bin" \
 * //   -F "user=JohnDoe" \
 * //   -F "timestamp=1678886400" \
 * //   http://127.0.0.1:8080/form-upload
 * httpserver.route('/form-upload', function(req, res) {
 *   const fields = req.saveMultipartFile('/app/code/data/uploaded_file.bin');
 *   // fields will be: { user: "JohnDoe", timestamp: "1678886400" }
 *   res.send(`File saved, user was ${fields.user}`);
 * });
 * 
 * @example
 * // Handle file download
 * httpserver.route('/download', function(req, res) {
 *   res.sendFile('/app/code/data/file.txt');
 * });
 */
httpserver.route = function (path, callback) {
    httpserver.init();
 
    // Wrap the user's callback in a try...catch block to handle uncaught exceptions
    const wrappedCallback = (req, res) => {
        try {
            callback(req, res);
        } catch (e) {
            try {
                res.send(JSON.stringify({
                    error: "Internal Server Error",
                    message: String(e)+"\n"+e.stack
                }), { "Content-Type": "application/json" });
            } catch (resError) {
            }
        }
    };
 
    server.route(path, wrappedCallback);
};
/**
 * Starts the HTTP server listening for connections. 
 * @param {number} port 
 */
httpserver.listen = function (port) {
    httpserver.init();
    server.listen(port);
}
/**
 * Loop the HTTP server
 */
httpserver.loop = function () {
    httpserver.init();
    server.loop();
}
/**
 * Serve static files
 * @param {string} path  The path to serve static files,should be start with '/'
 * @param {string} dir  The directory to serve static files,should be a absolute path start with '/app'
 */
httpserver.serveStatic = function (path, dir) {
    httpserver.init();
    if (!path) {
        path = '/';
    }
    if (!path.startsWith('/')) {
        path = '/' + path;
    }
    if (!path.endsWith('/')) {
        path = path + '/';
    }
    //path should be start with '/' and end with '/',or '/'
    server.serveStatic(path, dir);
}
/**
 * Deinitialize the server
 */
httpserver.deinit = function () {
    if (server) {
        server = null;
    }
}
/**
 * Get the native server object
 * @returns {Object} Native server object
 */
httpserver.getNative = function () {
    httpserver.init();
    return server;
}
export default httpserver;