minirps 0.1.2

Mini reverse proxy server written in rust
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# ![]assets/favicon.ico  Mini RPS
Mini [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) server
written in rust

## Features โค๏ธ
 - very fast single binary with no dependencies
 - static file server
 - [reverse proxy]https://en.wikipedia.org/wiki/Reverse_proxy router
 - HTTPS
 - [CORS]https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
 - consume any API data and create custom responses with [minijinja]https://github.com/mitsuhiko/minijinja templates
 - extensively tested with [hurl]https://github.com/Orange-OpenSource/hurl

## Motivation ๐Ÿ’ก
The best way to build servers and adjust to dynamic market demands in my
understanding is to apply the Linux philosophy to them.

Each server or microservice performs a specific and well-defined function.

So I decided to write a microservice that I can quickly configure to serve
files, and orchestrate other services.

## Install ๐Ÿ’ป
```
cargo install minirps
```

Alternatively you can use one of the precompiled binaries available with each
release (currently generic Linux only).

## Usage ๐ŸŽฎ
```
minirps -h
```

### Simple static file server
```
minirps path/to/static/folder
```

### Serve hidden files
```
minirps -a path/to/static/folder
```

### Ignore markdown files in root folder
```
minirps -i "/*.md" path/to/static/folder
```

### Ignore any markdown files
```
minirps -i "/**/*.md" path/to/static/folder
```

### Running on port 4000 instead of 3000
```
minirps -p 4000 path/to/static/folder
```

### Using https instead of http
```
minirps -p 4000 path/to/static/folder -c path/to/cert.pem -k path/to/key.pem
```

### Allow [CORS]https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS from all origins
```
minirps -o -p 4000 path/to/static/folder -c path/to/cert.pem -k path/to/key.pem
```

### Start the server with a config.toml file
Here the limit of possible configurations passed by command line has been reached.

To create more complex and interesting examples we need a `config.toml` file
```
minirps -f path/to/config.toml
```

### Ignore any markdown files and files starting with secret\_ in the root folder
config.toml
```toml
assets = "path/to/static/folder"
port = 4000
cert = "path/to/cert.pem"
key = "path/to/key.pem"
ignore = [
  "/**/*.md",
  "/secret_*"
]
```

### Allow [CORS]https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS for my website
config.toml
```toml
assets = "path/to/static/folder"
port = 4000
cert = "path/to/cert.pem"
key = "path/to/key.pem"
cors = [
  "https://www.my-website.com"
]
```

### Allow [CORS]https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS from my websites of varying origins 
config.toml
```toml
assets = "path/to/static/folder"
port = 4000
cert = "path/to/cert.pem"
key = "path/to/key.pem"
cors = [
  "http://www.my-website.com",
  "https://www.my-website.com",
  "http://www.my-other-website.com",
  "https://www.my-other-website.com"
]
```

### Allow [CORS]https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS from all origins
config.toml
```toml
assets = "path/to/static/folder"
port = 4000
cert = "path/to/cert.pem"
key = "path/to/key.pem"
cors = []
```

### Add a [reverse proxy]https://en.wikipedia.org/wiki/Reverse_proxy to an API server running at http://localhost:8000 
config.toml
```toml
assets = "path/to/static/folder"
port = 4000
cert = "path/to/cert.pem"
key = "path/to/key.pem"
cors = []

# GET https://localhost:4000/api/users => GET http://localhost:8000/users
[[routes]]
method = "GET"
path = "/api/users"

[[routes.requests]]
method = "GET"
url = "http://localhost:8000/users"

# PUT https://localhost:4000/api/users/21 => PUT http://localhost:8000/users/21
[[routes]]
method = "PUT"
path = "/api/users/:id"

[[routes.requests]]
method = "PUT"
url = "http://localhost:8000/users/{{params.id}}"
body = "{{body}}"
```

### Send a plain text response instead of the API response
config.toml
```toml
assets = "path/to/static/folder"
port = 4000
cert = "path/to/cert.pem"
key = "path/to/key.pem"
cors = []

# GET https://localhost:4000/api/users => GET http://localhost:8000/users
[[routes]]
method = "GET"
path = "/api/users"

[[routes.requests]]
name = "users"
method = "GET"
url = "http://localhost:8000/users"

[routes.response]
body = """
{% for user in data.users.json %}
  {{user.name}}
{% endfor %}"""
headers = { Content-Type = "text/plain" }

# PUT https://localhost:4000/api/users/21 => PUT http://localhost:8000/users/21
[[routes]]
method = "PUT"
path = "/api/users/:id"

[[routes.requests]]
name = "result"
method = "PUT"
url = "http://localhost:8000/users/{{params.id}}"
body = "{{body}}"

[routes.response]
body = "{% if data.result.status == 200 %}SUCCESS!{% else %}ERROR!{% endif %}"
headers = { Content-Type = "text/plain" }
```

### Send HTML template response instead of API response
config.toml
```toml
templates = "path/to/templates/folder"
assets = "path/to/static/folder"
port = 4000
cert = "path/to/cert.pem"
key = "path/to/key.pem"
cors = []

# GET https://localhost:4000/api/users => GET http://localhost:8000/users
[[routes]]
method = "GET"
path = "/api/users"

[[routes.requests]]
name = "users"
method = "GET"
url = "http://localhost:8000/users"

[routes.response]
body = "{% include 'users.html' %}"
headers = { Content-Type = "text/html" }

# PUT https://localhost:4000/api/users/21 => PUT http://localhost:8000/users/21
[[routes]]
method = "PUT"
path = "/api/users/:id"

[[routes.requests]]
name = "result"
method = "PUT"
url = "http://localhost:8000/users/{{params.id}}"
body = "{{body}}"

[routes.response]
body = "{% include 'edit.html' %}"
headers = { Content-Type = "text/html" }
```

## Examples ๐Ÿงช

### static server with cors
In this example, a static server was created and also a
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
request as a showcase.

Static server
```
minirps assets
```

[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) server
```
minirps assets/tests -o -p 4000 -c assets/certs/cert.txt -k assets/certs/key.txt
```

### starwars
In this example [minijinja](https://github.com/mitsuhiko/minijinja) templates
were used to consume data from [swapi's](https://swapi.dev/) Star Wars API.

```
minirps -f examples/starwars.toml
```

### test
In this example, a static server and some routes are built to test the use of
reverse proxy and templates automatically using
[hurl](https://github.com/Orange-OpenSource/hurl).

```
minirps -f examples/test.toml
```

```
hurl --test examples/test.hurl
```

## Docs ๐Ÿ“–
### config.toml
Command line arguments take priority over config file if both are present.  

Command line argument paths are relative to the current working directory.

`config.toml` paths are relative to your own directory.

Currently, any changes to `config.toml`, the server must be restarted for them to be applied.

#### port: integer?
Optional integer port number to run the server on, default: 3000

#### all: bool
Whether to display hidden files. 

In case of confirmation via the command line or `config.toml` they will be
displayed.

#### ignore: [string]?
List of files to ignore using glob expressions.

If the -i option is passed on the command line it will be appended to the list.

The routes must be considered in relation to the assets folder and not the
working directory.

For a complete reference of glob expressions and possible bugs check this
[library](https://github.com/devongovett/glob-match).

#### cors: [string]?
Optional array of strings representing allowed origins for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests.

An empty array allows all origins.

If this variable is not defined,[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) will be disabled.

#### cert: string?
Optional string with the public key file path for the https server.

Only if the `cert` and `key` are available will the server run over https.

#### key: string?
Optional string with the private key file path for the https server.

Only if the `cert` and `key` are available will the server run over https.

#### assets: string?
Optional string with the static files folder path.

#### templates: string?
Optional string with the path to the [minijinja](https://github.com/mitsuhiko/minijinja) templates folder.

#### routes: [{method: string, path: string, requests: [{...}]?, response: {...}?}]
Optional array of objects that define [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) routes:
 - `method` is a string with one of the http methods:
   - GET
   - POST
   - DELETE
   - PUT
   - PATCH
   - HEAD
   - OPTIONS
   - TRACE
   - CONNECT
 - `path` is a string with the path associated with the route, `:var` is acceptable for setting path variables (ex: /api/user/:id).

#### routes.requests: [{name: string?, method: string, headers: {header: string}?, url: string, body: string?}]?
Requests is an optional array of objects that represent requests that need to be made to generate the response.
 - `name` is an optional string that will be used (if present) to store the response data associated with the request to be made available in [minijinja]https://github.com/mitsuhiko/minijinja templates.
 - `method` is a required string containing the http method (or a [minijinja]https://github.com/mitsuhiko/minijinja template) as described in the routes definition.
 - `headers` is an object with the keys been the header to be setted in the request and the values a string containing the value of the header or a [minijinja]https://github.com/mitsuhiko/minijinja template to generate it.
 - `headers` is an object with the keys being the header to be configured in the request and the values being a string containing the header value or a [minijinja]https://github.com/mitsuhiko/minijinja template to generate it.
 - `url` is a required [minijinja]https://github.com/mitsuhiko/minijinja template or a raw string associated with the request.
 - `body` is an optional [minijinja]https://github.com/mitsuhiko/minijinja template or a raw string associated with the request.

#### routes.response {status: string?, headers: {header: string}?, body: string?}?
The response starts with the status, headers, and response body of the last request in the requests array, or if not present an empty 200 response, and the properties here are modifiers of the response sent by the [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) to the client.
 - `status` is an optional string or [minijinja]https://github.com/mitsuhiko/minijinja template that represents an integer to modify the status code of the response.
 - `headers` is an optional object where the keys are the headers to be modified in the response and the values are a string or a [minijinja]https://github.com/mitsuhiko/minijinja template representing the value associated with the header.
 - `body` is an optional string or [minijinja]https://github.com/mitsuhiko/minijinja template with the body to be replaced with the original response body.

### Available [minijinja]https://github.com/mitsuhiko/minijinja template variables

#### path: string
The associated path passed by the client in the request.

Ex.: `/api/user/:id` => `/api/user/25`.

#### query: string?
The associated query string or `none` passed by the client in the request.

Ex.: `http://localhost:3000/api/users?name=john` => `name=john`

#### headers: {header: string}
The associated object of the headers passed by the client in the request.

Note that all header keys are in **lowercase**.

Ex: Content-Type: text/plain => {"content-type": "text/plain"}

#### params: {param: string}
The associated object of the path params associated with the client request on a given route.

Ex: `/api/user/:id` => `http://localhost:3000/api/user/25` => {"id": "25"}

#### vars: {param: string}
The associated object of the query params associated with the client request.

Ex.: `http://localhost:3000/api/users?name=john` => `{"name": "john"}`

#### body: string
The body passed by the client in the request.

#### json
The body passed by the client in the request converted to json.

If it fails contains the body as a json string.

#### data: {name: {status: integer, headers: {header: string}, body: string, json}}
The data object is where all the results of the [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) request array are stored. A result is stored only if there is a name associated with it and will be available for the next request or response templates.

 - `name`: Object keys are the `name` passed in the `requests` array.
 - `status`: The response status associated with the request.
 - `headers`: The response headers associated with the request (the header name is always **lowercase** in this object)
 - `body`: The response body as a string associated with the request.
 - `json`: The response body converted to json (or if it fails as json string) associated with the request.

## Releases ๐Ÿ“ฆ
Currently, only binaries for generic versions of Linux are distributed across
releases.
```
sudo apt install pkg-config libssl-dev musl-tools
rustup update
rustup target add x86_64-unknown-linux-musl
cargo update
cargo build --release --target x86_64-unknown-linux-musl
```

## Microservices ๐Ÿ’ฏ
A list of microservices I use combined with `minirps` sharing yours philosophy.
 - [serialscale]https://github.com/marcodpt/serialscale: An IOT server
written in rust for reading weighing data on scales via serial port.
 - [rawprinter]https://github.com/marcodpt/rawprinter: An IOT server written
in rust for connecting via USB to raw printers.

## Contributing ๐Ÿค
It's a very simple project.
Any contribution, any feedback is greatly appreciated.

## Support โญ
If this project was useful to you, consider giving it a star on github, it's a
way to increase evidence and attract more contributors.

## Acknowledgment ๐Ÿ™
This work would not be possible if it were not for these related projects:
 - [minijinja]https://github.com/mitsuhiko/minijinja
 - [axum]https://github.com/tokio-rs/axum
 - [reqwest]https://github.com/seanmonstar/reqwest
 - [hurl]https://github.com/Orange-OpenSource/hurl
 - [serde]https://github.com/serde-rs/serde
 - [clap]https://github.com/clap-rs/clap
 - [glob-match]https://github.com/devongovett/glob-match

A huge thank you to all the people who contributed to these projects.