Skip to content

Commit

Permalink
Merge pull request #12 from pepe/docs
Browse files Browse the repository at this point in the history
New documentation
  • Loading branch information
bakpakin authored Oct 5, 2020
2 parents 470f792 + 4e27969 commit ba672e7
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 57 deletions.
156 changes: 131 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,156 @@
# circlet
# Circlet

Circlet is an HTTP and networking library for the [janet](https://github.com/janet-lang/janet) language.
It provides an abstraction out of the box like Clojure's [ring](https://github.com/ring-clojure/ring), which
is a server abstraction that makes it easy to build composable web applications.
Circlet is an HTTP and networking library for the
[janet](https://github.com/janet-lang/janet) language.
It provides an abstraction out of the box like Clojure's
[ring](https://github.com/ring-clojure/ring), which is a server abstraction
that makes it easy to build composable web applications.

Circlet uses [mongoose](https://cesanta.com/) as the underlying HTTP server engine. Mongoose
is a portable, low footprint, event based server library. The flexible build system requirements
of mongoose make it very easy to embed in other C programs and libraries.
Circlet uses [mongoose](https://cesanta.com/) as the underlying HTTP server
engine. Mongoose is a portable, low footprint, event based server library. The
flexible build system requirements of mongoose make it very easy to embed
in other C programs and libraries.

## Building
## Installing

Building requires [janet](https://github.com/janet-lang/janet) to be installed on the system, as
well as the `jpm` tool (installed by default with latest installs).
You can add Circlet as a dependency in your `project.janet`:

```sh
jpm build
```clojure
(declare-project
:name "web framework" :description "A framework for web development"
:dependencies ["https://github.com/janet-lang/circlet.git"])
```

You can also just run `jpm` to see a list of possible build commands.
You can also install it system-wide with `jpm`:

## Testing
```
sh jpm install circlet
```

Run a server on localhost with the following command
## Usage

```sh
jpm test
```
### Creating a server

You can create a HTTP server using the `circlet/server` function. The
function is of the form `(circlet/server handler port &opt ip-address)`
and takes the following parameters:

- `handler` function that takes the incoming HTTP request object (explained in
greater detail below) and returns the HTTP response object.
- `port` number of the port on which the server will listen for incoming
requests.
- `ip-address` optional string representing the IP address on which the server
will listen (defaults to `“127.0.0.1”`). The address `“*”` will
cause the server to listen on all available IP addresses.

The server runs immediately after creation.

### Request

The `handler` function takes a single parameter representing the request. The
request is a Janet table containing all the information about the request. It
contains the following keys:

- `:uri` requested URI
- `:method` HTTP method of the request as a Janet string (e.g. "GET", "POST")
- `:protocol` version of the HTTP protocol used for request
- `:headers` HTTP headers sent with the request as a Janet table. Keys in this
table are Janet strings with standard header's name (e.g. "Host", “Accept").
Values are the values in the HTTP header.
- `:body` body of the HTTP request
- `:query-string` query string part of the requested URI
- `:connection` internal mongoose connection serving this request

### Response

The return value of the `handler` function must be a Janet table containing
at least the `status` key with an integer value that corresponds to the HTTP
status of the response (e.g. 200 for success).

Other possible keys include:

- `:body` the body of the HTTP response (e.g. a string in HTML or JSON)
- `:headers` a Janet table or struct with standard HTTP headers. The structure
is the same as the HTTP request case described above.

There is also special key `:kind` you can use. There are two possible values for
this key:

- `:file` for serving a file from the filesystem. The filename is specified by
the `:file` key. You can specify `:mime` key with value of corresponding
mime type, it defaults to text/html.
- `:static` for serving static file from the filesystem. You have to provide
`:root` key with value of the path you want to serve.

### Middleware

Circlet also allows for the creation of different “middleware”. Pieces
of middleware can be thought of as links in a chain of functions that are
used to consume the HTTP request. The `handler` function can be thought of
as a piece of middleware and other middleware should match the signature and
return type of the `handler` function, i.e. accept and return a Janet table.

Middleware can be created in one of two ways. A user can define a function
with the appropriate signature and return type or use Circlet’s
`circlet/middleware` function to coerce an argument into a piece of
middleware. Middleware pieces are often higher-order functions (meaning
that they return another function). This allows for parameterization at
creation time.

#### Provided middleware

There are three basic pieces of middleware provided by Circlet:

- `(circlet/router routes)` simple routing facility. This function takes a
Janet table containing the routes. Each key should be a Janet string matching
a URI (e.g. `”/“`, `”/posts"`) with a value that is a function of the
same form as the `handler` function described above.
- `(circlet/logger nextmw)` simple logging facility. This function prints
the request info on `stdout`. The only argument is the next middleware.
- `(circlet/cookies nextmw)` middleware which extracts the cookies from the
HTTP header and stores the value under the `:cookies` key in the request
object.

## Example

The below example starts a very simple web server on port 8000.

```lisp
```clojure
(import circlet)

(defn myserver
"A simple HTTP server"
[req]
"A simple HTTP server" [request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "<!doctype html><html><body><h1>Hello.</h1></body></html>"})
:headers {"Content-Type" "text/html"} :body "<!doctype html><html><body><h1>Hello.</h1></body></html>"})

(circlet/server myserver 8000)
```

## Development

### Building

Building requires [Janet](https://github.com/janet-lang/janet) to be installed
on the system, as well as the `jpm` tool (installed by default with Janet).

```sh
jpm build
```

You can also just run `jpm` to see a list of possible build commands.

### Testing

Run a server on localhost with the following command

```sh
jpm test
```

This example is more involved, and shows all the functionality described in this
document.

## License

Unlike [janet](https://github.com/janet-lang/janet), circlet is licensed under
the GPL license in accordance with mongoose.
Unlike [janet](https://github.com/janet-lang/janet), Circlet is licensed
under the GPL license in accordance with mongoose.
44 changes: 25 additions & 19 deletions circlet_lib.janet
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@
(fn [&] x)))

(defn router
"Creates a router middleware"
"Creates a router middleware. Route parameter must be table or struct
where keys are URI paths and values are handler functions for given URI"
[routes]
(fn [req]
(fn [req]
(def r (or
(get routes (get req :uri))
(get routes :default)))
(if r ((middleware r) req) 404)))

(defn logger
"Creates a logging middleware"
"Creates a logging middleware. nextmw parameter is the handler function
of the next middleware"
[nextmw]
(fn [req]
(def {:uri uri
Expand All @@ -34,33 +36,37 @@
ret))

(defn cookies
"Parses cookies into the table under :cookies key"
"Parses cookies into the table under :cookies key. nextmw parameter is
the handler function of the next middleware"
[nextmw]
(def grammar
(peg/compile
(def grammar
(peg/compile
{:content '(some (if-not (set "=;") 1))
:eql "="
:eql "="
:sep '(between 1 2 (set "; "))
:main '(some (* (<- :content) :eql (<- :content) (? :sep)))}))
(fn [req]
(-> req
(put :cookies
(or (-?>> [:headers "Cookie"]
(get-in req)
(peg/match grammar)
(apply table))
{}))
nextmw)))
(put :cookies
(or (-?>> [:headers "Cookie"]
(get-in req)
(peg/match grammar)
(apply table))
{}))
nextmw)))

(defn server
"Creates a simple http server"
(defn server
"Creates a simple http server. handler parameter is the function handling the
requests. It could be middleware. port is the number of the port the server
will listen on. ip-address is optional IP address the server will listen on"
[handler port &opt ip-address]
(def mgr (manager))
(def mw (middleware handler))
(default ip-address "127.0.0.1")
(def interface (if (peg/match "*" ip-address)
(string port)
(string/format "%s:%d" ip-address port)))
(def interface
(if (peg/match "*" ip-address)
(string port)
(string/format "%s:%d" ip-address port)))
(defn evloop []
(print (string/format "Circlet server listening on [%s:%d] ..." ip-address port))
(var req (yield nil))
Expand Down
31 changes: 18 additions & 13 deletions test/testserv.janet
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
(import build/circlet :as circlet)

# Now build our server
(circlet/server
(circlet/server
(->
{"/thing" {:status 200
:headers {"Content-Type" "text/html; charset=utf-8"
"Thang" [1 2 3 4 5]}
:body "<!doctype html><html><body>
{"/thing" {:status 200
:headers {"Content-Type" "text/html; charset=utf-8"
"Thang" [1 2 3 4 5]}
:body "<!doctype html><html><body>
<h1>Is a thing.</h1>
<form action=\"bork\">
<input type=\"text\" name=\"firstname\">
<input type=\"submit\" value=\"Submit\">
</form>
</body></html>"}
"/blob" {:status 200
:body @"123\0123"}
"/redirect" {:status 302
:headers {"Location" "/thing"}}
:default {:kind :static
:root "."}}
circlet/router
circlet/logger)
"/bork" (fn [req]
(let [[fname] (peg/match '(* "firstname=" (<- (any 1)) -1) (req :query-string))]
{:status 200 :body (string "<!doctype html><html><body>Your firstname is "
fname "?</body></html>")}))
"/blob" {:status 200
:body @"123\0123"}
"/redirect" {:status 302
:headers {"Location" "/thing"}}
"/readme" {:kind :file :file "README.md" :mime "text/plain"}
:default {:kind :static
:root "."}}
circlet/router
circlet/logger)
8000)

0 comments on commit ba672e7

Please sign in to comment.