Cmdweb is a framework/runtime that allows backend web development in Batch. While there are much better languages, I sometimes give myself a little challenge to go back and write Batch scripts. A big problem that Batch has is the inability to do server-side work by itself, so I made this as a plugin for Batch to do so.
Disclaimer: Project is heavily in-dev and there might be vulnerabilities, do not use for anything serious!
- Install Node.js.
- Git clone this repo.
- Create a Batch file for your server, a Cmdweb project will look like this:
:: Get the necessary resources
call lib/init
:: Main code goes here
:: Start the js runtime
call lib/start <port> "<hostname>"
If not specified, port will be 8000 and hostname will be 127.0.0.1
.
Routing can be done as easily as:
call lib/listen "method" "/route" handler
Here, "method"
is any valid HTTP/1.1 method, "/route"
is self-explanatory, and handler
is the function (batch file) to call when there is a request coming at this route.
In a handler.bat
file, you must follow this template:
@echo off
call lib/get_request %~1
:: Main code goes here
call lib/respond %~1
@echo off
is just there for clean logs, and %~1
is the ID of the request. Calling lib/get_request
will get the information of this request, and calling lib/respond
sends the response to the client.
You can get the request body using these variables:
:: This variable contains the request body
echo %req.body%
:: URL (contains query params)
echo %req.url%
:: Path (does not contain query params)
echo %req.path%
:: Remote address
echo %req.address%
:: Headers are stored in a dictionary-like format, example:
echo %req.headers["content-type"]%
Here is how to create a response:
:: Response body
set res.body=Hello, World!
:: Status code, default is 200
set res.statusCode=200
:: Header JSON string
set res.headers={"content-type":"text/html"}
Though, note that Batch commands are limited to 8197 characters, so minus the variable length you can have around ~8100 characters in a variable. I will soon add streaming to solve this problem.
You can respond with a static file by providing a path in this variable:
set res.file=./path/to/file
Note that you must provide the content type yourself in the headers if you need.
Suppose you have a route like this:
call lib/listen "get" "/account/:name" get_account
In the handler, you can get the name
parameter:
echo %req.params.name%
With an url like http://bob.com/docs?page=30
, you can access the page
parameter like so:
echo %req.query.params.page%
Cmdweb also comes with arrays support: In the case where you have duplicated keys: http://bob.com/docs?page=30&page=31&page=32
, page will be parsed as an array, and you can access page
with variables following this format:
echo %req.query.params.page[0]%
echo %req.query.params.page[1]%
echo %req.query.params.page[2]%
Cmdweb supports message parsing based on specific formats and convert them into Batch variables to be used in a convenient way. You can provide a fourth optional argument to enable message parsing for that message format, for example:
call lib/listen "post" "/account" add_account "json"
Currently the only supported message formats are JSON and url encoded form data.
JSON messages will be parsed and stored into variables in a dictionary-like manner. For example, if we have a message payload like:
{
"name": "Bob",
"age": 19,
"profile": {
"isForeign": false,
"isRich": true
},
"jobs": [ "Baker", "Coder", "Investor" ]
}
You can access each property like this:
echo %req.body["name"]%
echo %req.body["age"]%
echo %req.body["profile"]["isForeign"]%
echo %req.body["profile"]["isRich"]%
echo %req.body["jobs"][0]%
echo %req.body["jobs"][1]%
echo %req.body["jobs"][2]%
The original JSON string is still available in req.body
. Like mentioned earlier, req.body
can only store ~8100 characters, but each of these variables can store ~8100 characters as well. Which means although a variable is limited, you can still have your message size be much larger if you design your message schema such that each property does not exceed 8100 characters.
Url encoded form data will also be parsed and stored into variables in a dictionary-like manner. For example, if we have a message payload like:
name=Bob+Ross&age=20&city=New+York
Here is how you can access each field:
echo %req.body["name"]%
echo %req.body["age"]%
echo %req.body["city"]%
And of course arrays are supported. For example, if the message is name=Bob+Ross&name=Steve+Jobs&name=Dennis+Ritchie
, you can access each field like so:
echo %req.body["name"][0]%
echo %req.body["name"][1]%
echo %req.body["name"][2]%
You can configure the maximum message size that each route will receive, default is 8100 bytes. Example:
:: This will limit the message size to 1048576 bytes, aka 1 megabyte
call lib/listen "post" "/update/account/:name" update_account "json" 1048576
Because characters like & > < | ^ % \n
might cause troubles in our Batch scripts, they are stored into variables in an escaped format:
& > < | ^
are escaped with^^^
.%
is escaped by duplicating itself.\n
is replaced with%NL%
- a built in macro of Cmdweb that can act as a new line.
You can load your ssl certificate and host a public https server:
call lib/start 443 "0.0.0.0" "./path/to/key" "./path/to/cert" "./path/to/ca"
Currently there are not many options to configure so you can call lib/start
and pass arguments directly like above, but there might be in the future. Therefore, you can also configure and start your server this way:
set server.port=443
set server.hostname=0.0.0.0
set server.ssl.keypath=./key.txt
set server.ssl.crtpath=./crt.txt
set server.ssl.capath=./ca.txt
call lib/start
Note that the variables above have higher priority than the arguments, so for example call lib/start 80
will still result in port 443 following the configuration above.
Cmdweb comes with a macro called NL
which is an equivalent to a new line. For example:
set req.body=Hello%NL%World
is equivalent to:
Hello
World
You can move all files in ./examples
to the current directory, then type app
in your console to start the server.
Note: The list is not in order.
- Serve/handle data through streams.
- Add message parsing for more message formats.
- A better way to configure for each route.
- A better way to respond/write to headers.
- Comfortable ways to deal with CORS.
- DB integration.
- Caching.
- Check for code injection possibilities.
Copyrights © 2024 Nguyen Phu Minh.
This project is licensed under the GPL 3.0 License.