Skip to content

Commit c49905d

Browse files
committed
Allow mutation in server-side service implementation
1 parent 61a2f8b commit c49905d

14 files changed

+77
-48
lines changed

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,44 +51,57 @@ Learn how to get up and running with Mojo on the [Modular website](https://www.m
5151
Once you have a Mojo project set up locally,
5252

5353
1. Add the `mojo-community` channel to your `mojoproject.toml`, e.g:
54+
5455
```toml
5556
[project]
5657
channels = ["conda-forge", "https://conda.modular.com/max", "https://repo.prefix.dev/mojo-community"]
5758
```
59+
5860
2. Add `lightbug_http` as a dependency:
61+
5962
```toml
6063
[dependencies]
6164
lightbug_http = ">=0.1.5"
6265
```
66+
6367
3. Run `magic install` at the root of your project, where `mojoproject.toml` is located
6468
4. Lightbug should now be installed as a dependency. You can import all the default imports at once, e.g:
69+
6570
```mojo
6671
from lightbug_http import *
6772
```
73+
6874
or import individual structs and functions, e.g.
75+
6976
```mojo
7077
from lightbug_http.service import HTTPService
7178
from lightbug_http.http import HTTPRequest, HTTPResponse, OK, NotFound
7279
```
80+
7381
there are some default handlers you can play with:
82+
7483
```mojo
7584
from lightbug_http.service import Printer # prints request details to console
7685
from lightbug_http.service import Welcome # serves an HTML file with an image (currently requires manually adding files to static folder, details below)
7786
from lightbug_http.service import ExampleRouter # serves /, /first, /second, and /echo routes
7887
```
88+
7989
5. Add your handler in `lightbug.🔥` by passing a struct that satisfies the following trait:
90+
8091
```mojo
8192
trait HTTPService:
82-
fn func(self, req: HTTPRequest) raises -> HTTPResponse:
93+
fn func(inout self, req: HTTPRequest) raises -> HTTPResponse:
8394
...
8495
```
96+
8597
For example, to make a `Printer` service that prints some details about the request to console:
98+
8699
```mojo
87100
from lightbug_http import *
88101
89102
@value
90103
struct Printer(HTTPService):
91-
fn func(self, req: HTTPRequest) raises -> HTTPResponse:
104+
fn func(inout self, req: HTTPRequest) raises -> HTTPResponse:
92105
var uri = req.uri
93106
print("Request URI: ", to_string(uri.request_uri))
94107
@@ -104,7 +117,9 @@ Once you have a Mojo project set up locally,
104117
105118
return OK(body)
106119
```
107-
6. Start a server listening on a port with your service like so.
120+
121+
6. Start a server listening on a port with your service like so.
122+
108123
```mojo
109124
from lightbug_http import Welcome, Server
110125
@@ -113,20 +128,22 @@ Once you have a Mojo project set up locally,
113128
var handler = Welcome()
114129
server.listen_and_serve("0.0.0.0:8080", handler)
115130
```
131+
116132
Feel free to change the settings in `listen_and_serve()` to serve on a particular host and port.
117133
118134
Now send a request `0.0.0.0:8080`. You should see some details about the request printed out to the console.
119-
135+
120136
Congrats 🥳 You're using Lightbug!
121137
122138
123139
Routing is not in scope for this library, but you can easily set up routes yourself:
140+
124141
```mojo
125142
from lightbug_http import *
126143
127144
@value
128145
struct ExampleRouter(HTTPService):
129-
fn func(self, req: HTTPRequest) raises -> HTTPResponse:
146+
fn func(inout self, req: HTTPRequest) raises -> HTTPResponse:
130147
var body = req.body_raw
131148
var uri = req.uri
132149
@@ -156,7 +173,7 @@ from lightbug_http import *
156173
157174
@value
158175
struct Welcome(HTTPService):
159-
fn func(self, req: HTTPRequest) raises -> HTTPResponse:
176+
fn func(inout self, req: HTTPRequest) raises -> HTTPResponse:
160177
var uri = req.uri
161178
162179
if uri.path == "/":
@@ -216,10 +233,13 @@ fn main() -> None:
216233
Pure Mojo-based client is available by default. This client is also used internally for testing the server.
217234

218235
## Switching between pure Mojo and Python implementations
236+
219237
By default, Lightbug uses the pure Mojo implementation for networking. To use Python's `socket` library instead, just import the `PythonServer` instead of the `Server` with the following line:
238+
220239
```mojo
221240
from lightbug_http.python.server import PythonServer
222241
```
242+
223243
You can then use all the regular server commands in the same way as with the default server.
224244
Note: as of September, 2024, `PythonServer` and `PythonClient` throw a compilation error when starting. There's an open [issue](https://github.com/saviorand/lightbug_http/issues/41) to fix this - contributions welcome!
225245

lightbug_http/__init__.mojo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from lightbug_http.http import HTTPRequest, HTTPResponse, OK, NotFound, StatusCo
22
from lightbug_http.uri import URI
33
from lightbug_http.header import Header, Headers, HeaderKey
44
from lightbug_http.cookie import Cookie, RequestCookieJar, ResponseCookieJar
5-
from lightbug_http.service import HTTPService, Welcome
5+
from lightbug_http.service import HTTPService, Welcome, Counter
66
from lightbug_http.server import Server
77
from lightbug_http.strings import to_string
88

lightbug_http/cookie/cookie.mojo

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections import Optional
22
from lightbug_http.header import HeaderKey
33

4+
45
struct Cookie(CollectionElement):
56
alias EXPIRES = "Expires"
67
alias MAX_AGE = "Max-Age"
@@ -25,7 +26,6 @@ struct Cookie(CollectionElement):
2526
var path: Optional[String]
2627
var max_age: Optional[Duration]
2728

28-
2929
@staticmethod
3030
fn from_set_header(header_str: String) raises -> Self:
3131
var parts = header_str.split(Cookie.SEPERATOR)
@@ -55,7 +55,7 @@ struct Cookie(CollectionElement):
5555
elif part.startswith(Cookie.MAX_AGE):
5656
cookie.max_age = Duration.from_string(part.removeprefix(Cookie.MAX_AGE + Cookie.EQUAL))
5757
elif part.startswith(Cookie.EXPIRES):
58-
var expires = Expiration.from_string(part.removeprefix(Cookie.EXPIRES + Cookie.EQUAL))
58+
var expires = Expiration.from_string(part.removeprefix(Cookie.EXPIRES + Cookie.EQUAL))
5959
if expires:
6060
cookie.expires = expires.value()
6161

@@ -86,7 +86,7 @@ struct Cookie(CollectionElement):
8686
self.partitioned = partitioned
8787

8888
fn __str__(self) -> String:
89-
return "Name: " + self.name + " Value: " + self.value
89+
return "Name: " + self.name + " Value: " + self.value
9090

9191
fn __copyinit__(inout self: Cookie, existing: Cookie):
9292
self.name = existing.name
@@ -120,7 +120,6 @@ struct Cookie(CollectionElement):
120120
return Header(HeaderKey.SET_COOKIE, self.build_header_value())
121121

122122
fn build_header_value(self) -> String:
123-
124123
var header_value = self.name + Cookie.EQUAL + self.value
125124
if self.expires.is_datetime():
126125
var v = self.expires.http_date_timestamp()

lightbug_http/cookie/duration.mojo

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
@value
2-
struct Duration():
2+
struct Duration:
33
var total_seconds: Int
44

5-
fn __init__(
6-
inout self,
7-
seconds: Int = 0,
8-
minutes: Int = 0,
9-
hours: Int = 0,
10-
days: Int = 0
11-
):
5+
fn __init__(inout self, seconds: Int = 0, minutes: Int = 0, hours: Int = 0, days: Int = 0):
126
self.total_seconds = seconds
137
self.total_seconds += minutes * 60
148
self.total_seconds += hours * 60 * 60

lightbug_http/cookie/expiration.mojo

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
alias HTTP_DATE_FORMAT = "ddd, DD MMM YYYY HH:mm:ss ZZZ"
22
alias TZ_GMT = TimeZone(0, "GMT")
33

4+
from small_time import SmallTime
5+
6+
47
@value
5-
struct Expiration:
8+
struct Expiration(CollectionElement):
69
var variant: UInt8
710
var datetime: Optional[SmallTime]
811

@@ -15,7 +18,7 @@ struct Expiration:
1518
return Self(variant=1, datetime=time)
1619

1720
@staticmethod
18-
fn from_string(str: String) -> Optional[Self]:
21+
fn from_string(str: String) -> Optional[Expiration]:
1922
try:
2023
return Self.from_datetime(strptime(str, HTTP_DATE_FORMAT, TZ_GMT))
2124
except:
@@ -44,5 +47,10 @@ struct Expiration:
4447
if self.variant != other.variant:
4548
return False
4649
if self.variant == 1:
47-
return self.datetime == other.datetime
50+
if bool(self.datetime) != bool(other.datetime):
51+
return False
52+
elif not bool(self.datetime) and not bool(other.datetime):
53+
return True
54+
return self.datetime.value().isoformat() == other.datetime.value().isoformat()
55+
4856
return True

lightbug_http/cookie/request_cookie_jar.mojo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from lightbug_http.strings import to_string, lineBreak
55
from lightbug_http.header import HeaderKey, write_header
66
from lightbug_http.utils import ByteReader, ByteWriter, is_newline, is_space
77

8+
89
@value
910
struct RequestCookieJar(Formattable, Stringable):
1011
var _inner: Dict[String, String]
@@ -34,7 +35,6 @@ struct RequestCookieJar(Formattable, Stringable):
3435
# TODO value must be "unquoted"
3536
self._inner[key] = value
3637

37-
3838
@always_inline
3939
fn empty(self) -> Bool:
4040
return len(self._inner) == 0

lightbug_http/cookie/response_cookie_jar.mojo

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct ResponseCookieKey(KeyElement):
1414
inout self,
1515
name: String,
1616
domain: Optional[String] = Optional[String](None),
17-
path: Optional[String] = Optional[String](None)
17+
path: Optional[String] = Optional[String](None),
1818
):
1919
self.name = name
2020
self.domain = domain.or_else("")
@@ -39,6 +39,7 @@ struct ResponseCookieKey(KeyElement):
3939
fn __hash__(self: Self) -> UInt:
4040
return hash(self.name + "~" + self.domain + "~" + self.path)
4141

42+
4243
@value
4344
struct ResponseCookieJar(Formattable, Stringable):
4445
var _inner: Dict[ResponseCookieKey, Cookie]
@@ -76,7 +77,7 @@ struct ResponseCookieJar(Formattable, Stringable):
7677
return to_string(self)
7778

7879
fn __len__(self) -> Int:
79-
return len(self._inner)
80+
return len(self._inner)
8081

8182
@always_inline
8283
fn set_cookie(inout self, cookie: Cookie):
@@ -86,7 +87,6 @@ struct ResponseCookieJar(Formattable, Stringable):
8687
fn empty(self) -> Bool:
8788
return len(self) == 0
8889

89-
9090
fn from_headers(inout self, headers: List[String]) raises:
9191
for header in headers:
9292
try:
@@ -97,10 +97,9 @@ struct ResponseCookieJar(Formattable, Stringable):
9797
fn encode_to(inout self, inout writer: ByteWriter):
9898
for cookie in self._inner.values():
9999
var v = cookie[].build_header_value()
100-
write_header(writer, HeaderKey.SET_COOKIE , v)
101-
100+
write_header(writer, HeaderKey.SET_COOKIE, v)
102101

103102
fn format_to(self, inout writer: Formatter):
104103
for cookie in self._inner.values():
105104
var v = cookie[].build_header_value()
106-
write_header(writer, HeaderKey.SET_COOKIE , v)
105+
write_header(writer, HeaderKey.SET_COOKIE, v)

lightbug_http/cookie/same_site.mojo

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@value
2-
struct SameSite():
3-
var value : UInt8
2+
struct SameSite:
3+
var value: UInt8
44

55
alias none = SameSite(0)
66
alias lax = SameSite(1)

lightbug_http/http/request.mojo

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct HTTPRequest(Formattable, Stringable):
4040
var protocol: String
4141
var uri_str: String
4242
try:
43-
var rest = headers.parse_raw(reader)
43+
var rest = headers.parse_raw(reader)
4444
method, uri_str, protocol = rest[0], rest[1], rest[2]
4545
except e:
4646
raise Error("Failed to parse request headers: " + e.__str__())
@@ -89,7 +89,6 @@ struct HTTPRequest(Formattable, Stringable):
8989
if HeaderKey.HOST not in self.headers:
9090
self.headers[HeaderKey.HOST] = uri.host
9191

92-
9392
fn set_connection_close(inout self):
9493
self.headers[HeaderKey.CONNECTION] = "close"
9594

lightbug_http/http/response.mojo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ struct HTTPResponse(Formattable, Stringable):
4747
var status_text: String
4848

4949
try:
50-
var properties = headers.parse_raw(reader)
50+
var properties = headers.parse_raw(reader)
5151
protocol, status_code, status_text = properties[0], properties[1], properties[2]
5252
cookies.from_headers(properties[3])
5353
reader.skip_newlines()

lightbug_http/server.mojo

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ struct Server:
119119
concurrency = DefaultConcurrency
120120
return concurrency
121121

122-
fn listen_and_serve[T: HTTPService](inout self, address: String, handler: T) raises -> None:
122+
fn listen_and_serve[T: HTTPService](inout self, address: String, inout handler: T) raises -> None:
123123
"""
124124
Listen for incoming connections and serve HTTP requests.
125125
@@ -132,7 +132,7 @@ struct Server:
132132
_ = self.set_address(address)
133133
self.serve(listener, handler)
134134

135-
fn serve[T: HTTPService](inout self, ln: NoTLSListener, handler: T) raises -> None:
135+
fn serve[T: HTTPService](inout self, ln: NoTLSListener, inout handler: T) raises -> None:
136136
"""
137137
Serve HTTP requests.
138138
@@ -149,7 +149,7 @@ struct Server:
149149
var conn = self.ln.accept()
150150
self.serve_connection(conn, handler)
151151

152-
fn serve_connection[T: HTTPService](inout self, conn: SysConnection, handler: T) raises -> None:
152+
fn serve_connection[T: HTTPService](inout self, conn: SysConnection, inout handler: T) raises -> None:
153153
"""
154154
Serve a single connection.
155155

0 commit comments

Comments
 (0)