Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/laytan/odin-http
A HTTP/1.1 client/server implementation for Odin.
https://github.com/laytan/odin-http
http http-client http-server odin-lib
Last synced: 9 days ago
JSON representation
A HTTP/1.1 client/server implementation for Odin.
- Host: GitHub
- URL: https://github.com/laytan/odin-http
- Owner: laytan
- License: mit
- Created: 2023-05-07T20:56:41.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-30T12:32:51.000Z (about 1 month ago)
- Last Synced: 2024-11-26T03:20:51.606Z (18 days ago)
- Topics: http, http-client, http-server, odin-lib
- Language: Odin
- Homepage: https://odin-http.laytan.dev/
- Size: 20 MB
- Stars: 144
- Watchers: 2
- Forks: 14
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-odin - Odin HTTP - http/blob/main/LICENSE) | Webdev, Server, Networking (Libraries / Networking)
- awesome-odin - Odin HTTP - http/blob/main/LICENSE) | Webdev, Server, Networking (Libraries / Networking)
README
# Odin HTTP
A HTTP/1.1 implementation for Odin purely written in Odin (besides SSL).
See generated package documentation at [odin-http.laytan.dev](https://odin-http.laytan.dev).
See below examples or the examples directory.
## Compatibility
This is beta software, confirmed to work in my own use cases but can certainly contain edge cases and bugs that I did not catch.
Please file issues for any bug or suggestion you encounter/have.I am usually on a recent master version of Odin and commits will be made with new features if applicable, backwards compatibility or even
stable version compatibility is not currently a thing.Because this is still heavily in development, I do not hesitate to push API changes at the moment, so beware.
The package has been tested to work with Ubuntu Linux (other "normal" distros should work), MacOS (m1 and intel), and Windows 64 bit.
Any other distributions or versions have not been tested and might not work.## Dependencies
The *client* package depends on OpenSSL for making HTTPS requests.
This repository contains a copy of these libraries for ease of use on Windows.
For Linux, most distros come with OpenSSL, if not you can install it with a package manager, usually under `libssl3`.
## Performance
Some small benchmarks have been done in the comparisons directory.
My main priority in terms of performance is currently Linux (because most servers end up there in production).
Other targets are still made to be performant, but benchmarking etc. is mostly done on Linux.
## IO implementations
Although these implementation details are not exposed when using the package, these are the underlying kernel API's that are used.
- Windows: [IOCP (IO Completion Ports)](https://en.wikipedia.org/wiki/Input/output_completion_port)
- Linux: [io_uring](https://en.wikipedia.org/wiki/Io_uring)
- Darwin: [KQueue](https://en.wikipedia.org/wiki/Kqueue)The IO part of this package can be used on its own for other types of applications, see the nbio directory for the documentation on that.
It has APIs for reading, writing, opening, closing, seeking files and accepting, connecting, sending, receiving and closing sockets, both UDP and TCP, fully cross-platform.## Server example
```odin
package mainimport "core:fmt"
import "core:log"
import "core:net"
import "core:time"import http "../.." // Change to path of package.
main :: proc() {
context.logger = log.create_console_logger(.Info)s: http.Server
// Register a graceful shutdown when the program receives a SIGINT signal.
http.server_shutdown_on_interrupt(&s)// Set up routing
router: http.Router
http.router_init(&router)
defer http.router_destroy(&router)// Routes are tried in order.
// Route matching is implemented using an implementation of Lua patterns, see the docs on them here:
// https://www.lua.org/pil/20.2.html
// They are very similar to regex patterns but a bit more limited, which makes them much easier to implement since Odin does not have a regex implementation.// Matches /users followed by any word (alphanumeric) followed by /comments and then / with any number.
// The word is available as req.url_params[0], and the number as req.url_params[1].
http.route_get(&router, "/users/(%w+)/comments/(%d+)", http.handler(proc(req: ^http.Request, res: ^http.Response) {
http.respond_plain(res, fmt.tprintf("user %s, comment: %s", req.url_params[0], req.url_params[1]))
}))
http.route_get(&router, "/cookies", http.handler(cookies))
http.route_get(&router, "/api", http.handler(api))
http.route_get(&router, "/ping", http.handler(ping))
http.route_get(&router, "/index", http.handler(index))// Matches every get request that did not match another route.
http.route_get(&router, "(.*)", http.handler(static))http.route_post(&router, "/ping", http.handler(post_ping))
routed := http.router_handler(&router)
log.info("Listening on http://localhost:6969")
err := http.listen_and_serve(&s, routed, net.Endpoint{address = net.IP4_Loopback, port = 6969})
fmt.assertf(err == nil, "server stopped with error: %v", err)
}cookies :: proc(req: ^http.Request, res: ^http.Response) {
append(
&res.cookies,
http.Cookie{
name = "Session",
value = "123",
expires_gmt = time.now(),
max_age_secs = 10,
http_only = true,
same_site = .Lax,
},
)
http.respond_plain(res, "Yo!")
}api :: proc(req: ^http.Request, res: ^http.Response) {
if err := http.respond_json(res, req.line); err != nil {
log.errorf("could not respond with JSON: %s", err)
}
}ping :: proc(req: ^http.Request, res: ^http.Response) {
http.respond_plain(res, "pong")
}index :: proc(req: ^http.Request, res: ^http.Response) {
http.respond_file(res, "examples/complete/static/index.html")
}static :: proc(req: ^http.Request, res: ^http.Response) {
http.respond_dir(res, "/", "examples/complete/static", req.url_params[0])
}post_ping :: proc(req: ^http.Request, res: ^http.Response) {
http.body(req, len("ping"), res, proc(res: rawptr, body: http.Body, err: http.Body_Error) {
res := cast(^http.Response)resif err != nil {
http.respond(res, http.body_error_status(err))
return
}if body != "ping" {
http.respond(res, http.Status.Unprocessable_Content)
return
}http.respond_plain(res, "pong")
})
}
```## Client example
```odin
package mainimport "core:fmt"
import "../../client"
main :: proc() {
get()
post()
}// basic get request.
get :: proc() {
res, err := client.get("https://www.google.com/")
if err != nil {
fmt.printf("Request failed: %s", err)
return
}
defer client.response_destroy(&res)fmt.printf("Status: %s\n", res.status)
fmt.printf("Headers: %v\n", res.headers)
fmt.printf("Cookies: %v\n", res.cookies)
body, allocation, berr := client.response_body(&res)
if berr != nil {
fmt.printf("Error retrieving response body: %s", berr)
return
}
defer client.body_destroy(body, allocation)fmt.println(body)
}Post_Body :: struct {
name: string,
message: string,
}// POST request with JSON.
post :: proc() {
req: client.Request
client.request_init(&req, .Post)
defer client.request_destroy(&req)pbody := Post_Body{"Laytan", "Hello, World!"}
if err := client.with_json(&req, pbody); err != nil {
fmt.printf("JSON error: %s", err)
return
}res, err := client.request(&req, "https://webhook.site/YOUR-ID-HERE")
if err != nil {
fmt.printf("Request failed: %s", err)
return
}
defer client.response_destroy(&res)fmt.printf("Status: %s\n", res.status)
fmt.printf("Headers: %v\n", res.headers)
fmt.printf("Cookies: %v\n", res.cookies)body, allocation, berr := client.response_body(&res)
if berr != nil {
fmt.printf("Error retrieving response body: %s", berr)
return
}
defer client.body_destroy(body, allocation)fmt.println(body)
}
```