Skip to content

Commit accdcb2

Browse files
authored
Merge pull request #33 from JoseLucasapp/http
Create websites with zumbra
2 parents bd0ffba + d9a6bd5 commit accdcb2

File tree

12 files changed

+415
-1
lines changed

12 files changed

+415
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
registerRoute("/", "<h1>Home</h1>");
2+
registerRoute("/about", "<h1>Sobre</h1>");
3+
registerRoute("/contact", "<h1>Contato</h1>");
4+
5+
server(3333);

code_examples/http/get.zum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
var getIp << get("https://httpbin.org/ip");
2+
var json << json_parse(getIp["body"]);
3+
show(json["origin"]);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
h1 {
2+
color: red;
3+
text-align: center;
4+
}

code_examples/http/html/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Zumbra</title>
7+
<link rel="stylesheet" href="/static/style.css">
8+
</head>
9+
<body>
10+
<h1>Hello World</h1>
11+
<p>This is a test</p>
12+
</body>
13+
</html>

code_examples/http/htmlOnZumbra.zum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
registerRoute("GET", "/", html("<h1>Home</h1>"));
2+
server(3333);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
serveStatic("/static", "./code_examples/http/html/assets");
2+
registerRoute("GET", "/", serveFile("./code_examples/http/html/index.html", {"name": "Zumbra"}));
3+
server(3333);

evaluator/builtins.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func init() {
1515
"indexOf", "addToDict", "deleteFromDict",
1616
"toString", "toInt", "toFloat", "toBool", "date", "organize", "toUppercase", "toLowercase", "capitalize",
1717
"removeWhiteSpaces", "sum", "bhaskara", "getFromDict", "sendEmail", "randomInteger", "randomFloat", "sendWhatsapp",
18-
"dictKeys", "dictValues", "replace",
18+
"dictKeys", "dictValues", "replace", "server", "get", "json_parse", "registerRoute", "html", "serveFile", "serveStatic",
1919
}
2020

2121
for _, name := range names {

index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Zumbra</title>
7+
</head>
8+
<body>
9+
<h1>Hello World</h1>
10+
<p>This is a zumbra test</p>
11+
</body>
12+
</html>

object/builtins/builtins.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,27 @@ var Builtins = []struct {
112112
{
113113
"replace", ReplaceBuiltin(),
114114
},
115+
{
116+
"server", CreateServerBuiltin(),
117+
},
118+
{
119+
"get", GetBuiltin(),
120+
},
121+
{
122+
"jsonParse", JsonParse(),
123+
},
124+
{
125+
"registerRoute", RegisterRoutesBuiltin(),
126+
},
127+
{
128+
"html", HtmlHandlerBuiltin(),
129+
},
130+
{
131+
"serveFile", ServeFileBuiltin(),
132+
},
133+
{
134+
"serveStatic", ServerStaticBuiltin(),
135+
},
115136
}
116137

117138
func NewBoolean(value bool) *object.Boolean {

object/builtins/http_builtins.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package builtins
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"strings"
8+
"zumbra/object"
9+
)
10+
11+
type Route struct {
12+
Method string
13+
Path string
14+
HandlerBody object.Object
15+
Middlewares []func(http.ResponseWriter, *http.Request) bool
16+
}
17+
18+
type StaticRoute struct {
19+
RoutePrefix string
20+
StaticDir string
21+
}
22+
23+
var staticRoutes []StaticRoute
24+
25+
var registerRoutes []Route
26+
27+
func CreateServerBuiltin() *object.Builtin {
28+
return &object.Builtin{
29+
Fn: func(args ...object.Object) object.Object {
30+
31+
if len(args) != 1 {
32+
return NewError("wrong number of arguments. got=%d, want=4", len(args))
33+
}
34+
35+
portObj, ok := args[0].(*object.Integer)
36+
if !ok {
37+
return NewError("argument to `server` must be INTEGER, got %s", args[0].Type())
38+
}
39+
40+
for _, sr := range staticRoutes {
41+
http.Handle(sr.RoutePrefix+"/", http.StripPrefix(sr.RoutePrefix, http.FileServer(http.Dir(sr.StaticDir))))
42+
}
43+
44+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
45+
route := matchRoute(r)
46+
47+
if route == nil {
48+
http.NotFound(w, r)
49+
return
50+
}
51+
52+
for _, mw := range route.Middlewares {
53+
if !mw(w, r) {
54+
return
55+
}
56+
}
57+
58+
switch handler := route.HandlerBody.(type) {
59+
case *object.String:
60+
w.Write([]byte(handler.Value))
61+
case *object.Builtin:
62+
result := handler.Fn()
63+
if str, ok := result.(*object.String); ok {
64+
w.Write([]byte(str.Value))
65+
} else {
66+
w.Write([]byte("function did not return string"))
67+
}
68+
default:
69+
w.Write([]byte("unsupported handler type"))
70+
}
71+
})
72+
73+
portStr := fmt.Sprintf("%d", portObj.Value)
74+
if err := http.ListenAndServe(":"+portStr, nil); err != nil {
75+
return NewError("Failed to start server on port %s. got %s", portStr, err)
76+
}
77+
78+
return nil
79+
},
80+
}
81+
}
82+
83+
func GetBuiltin() *object.Builtin {
84+
return &object.Builtin{
85+
Fn: func(args ...object.Object) object.Object {
86+
87+
if len(args) != 1 {
88+
return NewError("wrong number of arguments. got=%d, want=1", len(args))
89+
}
90+
91+
if args[0].Type() != object.STRING_OBJ {
92+
return NewError("argument to `get` must be STRING, got %s", args[0].Type())
93+
}
94+
95+
resp, err := http.Get(args[0].(*object.String).Value)
96+
if err != nil {
97+
return NewError("Failed to get, get('%s'). got %s", args[0].(*object.String).Value, err)
98+
}
99+
100+
defer resp.Body.Close()
101+
102+
body, err := io.ReadAll(resp.Body)
103+
if err != nil {
104+
return NewError("Failed to read body, get('%s'). got %s", args[0].(*object.String).Value, err)
105+
}
106+
107+
return &object.Dict{Pairs: map[object.DictKey]object.DictPair{
108+
(&object.String{Value: "body"}).DictKey(): {
109+
Key: &object.String{Value: "body"},
110+
Value: &object.String{Value: string(body)},
111+
},
112+
}}
113+
},
114+
}
115+
}
116+
117+
func RegisterRoutesBuiltin() *object.Builtin {
118+
return &object.Builtin{
119+
Fn: func(args ...object.Object) object.Object {
120+
121+
if len(args) != 3 {
122+
return NewError("wrong number of arguments. got=%d, want=2", len(args))
123+
}
124+
125+
method, ok1 := args[0].(*object.String)
126+
127+
path, ok2 := args[1].(*object.String)
128+
handler := args[2]
129+
130+
if !ok1 || !ok2 {
131+
return NewError("method and path must be STRING")
132+
}
133+
134+
registerRoutes = append(registerRoutes, Route{
135+
Method: strings.ToUpper(method.Value),
136+
Path: path.Value,
137+
HandlerBody: handler,
138+
Middlewares: nil,
139+
})
140+
141+
return nil
142+
},
143+
}
144+
}
145+
146+
func UseMiddlewaresBuiltin() *object.Builtin {
147+
return &object.Builtin{
148+
Fn: func(args ...object.Object) object.Object {
149+
if len(args) != 2 {
150+
return NewError("wrong number of arguments. got=%d, want=2", len(args))
151+
}
152+
153+
path, ok1 := args[0].(*object.String)
154+
middlewareName, ok2 := args[1].(*object.String)
155+
156+
if !ok1 || !ok2 {
157+
return NewError("method and path must be STRING")
158+
}
159+
160+
for i, route := range registerRoutes {
161+
if route.Path == path.Value {
162+
if middlewareName.Value == "logger" {
163+
registerRoutes[i].Middlewares = append(registerRoutes[i].Middlewares, func(w http.ResponseWriter, r *http.Request) bool {
164+
fmt.Println("Request: ", r.Method, r.URL.Path)
165+
return true
166+
})
167+
}
168+
}
169+
}
170+
171+
return nil
172+
},
173+
}
174+
}
175+
176+
func HtmlHandlerBuiltin() *object.Builtin {
177+
return &object.Builtin{
178+
Fn: func(args ...object.Object) object.Object {
179+
if len(args) != 1 {
180+
return NewError("html(content) expects 1 argument")
181+
}
182+
183+
str, ok := args[0].(*object.String)
184+
if !ok {
185+
return NewError("html(content) expects a STRING")
186+
}
187+
188+
return &object.Builtin{
189+
Fn: func(args ...object.Object) object.Object {
190+
return &object.String{Value: str.Value}
191+
},
192+
}
193+
},
194+
}
195+
}
196+
197+
func ServerStaticBuiltin() *object.Builtin {
198+
return &object.Builtin{
199+
Fn: func(args ...object.Object) object.Object {
200+
if len(args) != 2 {
201+
return NewError("wrong number of arguments. got=%d, want=2", len(args))
202+
}
203+
204+
prefix, ok1 := args[0].(*object.String)
205+
dir, ok2 := args[1].(*object.String)
206+
207+
if !ok1 || !ok2 {
208+
return NewError("method and path must be STRING")
209+
}
210+
211+
staticRoutes = append(staticRoutes, StaticRoute{
212+
RoutePrefix: prefix.Value,
213+
StaticDir: dir.Value,
214+
})
215+
216+
return nil
217+
},
218+
}
219+
}
220+
221+
func matchRoute(r *http.Request) *Route {
222+
for _, route := range registerRoutes {
223+
if route.Method != r.Method {
224+
continue
225+
}
226+
227+
reqParts := strings.Split(r.URL.Path, "/")
228+
routeParts := strings.Split(route.Path, "/")
229+
230+
if len(reqParts) != len(routeParts) {
231+
continue
232+
}
233+
234+
match := true
235+
for i := 0; i < len(reqParts); i++ {
236+
if strings.HasPrefix(routeParts[i], ":") {
237+
continue
238+
}
239+
240+
if reqParts[i] != routeParts[i] {
241+
match = false
242+
break
243+
}
244+
}
245+
246+
if match {
247+
return &route
248+
}
249+
}
250+
return nil
251+
}

0 commit comments

Comments
 (0)