Initial commit
95
examples/board/main.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import "log"
|
||||
import "sync"
|
||||
import "path"
|
||||
import "net/http"
|
||||
import "crypto/tls"
|
||||
import "html/template"
|
||||
import "hnakra/service"
|
||||
|
||||
type Post struct {
|
||||
Author, Content string
|
||||
Next *Post
|
||||
}
|
||||
|
||||
type Board struct {
|
||||
root string
|
||||
mount *service.HTTP
|
||||
mux *http.ServeMux
|
||||
template *template.Template
|
||||
|
||||
postsMutex sync.RWMutex
|
||||
posts *Post
|
||||
}
|
||||
|
||||
func main () {
|
||||
board := Board { root: "/board/" }
|
||||
board.mux = http.NewServeMux()
|
||||
board.mount = &service.HTTP {
|
||||
Mount: service.Mount {
|
||||
Path: board.root,
|
||||
Name: "Board",
|
||||
Description: "A board where you can post things.",
|
||||
TLSConfig: &tls.Config { InsecureSkipVerify: true },
|
||||
},
|
||||
Handler: board.mux,
|
||||
}
|
||||
handle := func (pattern string, handler func (http.ResponseWriter, *http.Request)) {
|
||||
board.mux.HandleFunc(pattern, handler)
|
||||
}
|
||||
handle(board.root, board.serveHome)
|
||||
handle(path.Join(board.root, "actionPost"), board.serveActionPost)
|
||||
board.template = template.Must(template.New("board").Parse(tem))
|
||||
|
||||
err := board.mount.Run()
|
||||
if err != nil {
|
||||
log.Println("XXX", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (board *Board) getPosts (max int) []*Post {
|
||||
board.postsMutex.RLock()
|
||||
defer board.postsMutex.RUnlock()
|
||||
|
||||
posts := make([]*Post, max)
|
||||
count := 0
|
||||
current := board.posts
|
||||
for current != nil && count < max {
|
||||
posts[count] = current
|
||||
count ++
|
||||
current = current.Next
|
||||
}
|
||||
return posts[:count]
|
||||
}
|
||||
|
||||
func (board *Board) serveHome (res http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path != board.root {
|
||||
http.NotFoundHandler().ServeHTTP(res, req)
|
||||
return
|
||||
}
|
||||
res.Header().Add("content-type", "text/html")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
board.template.ExecuteTemplate(res, "pageHome", board.getPosts(16))
|
||||
}
|
||||
|
||||
func (board *Board) serveActionPost (res http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
res.Header().Add("content-type", "text/plain")
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
res.Write([]byte("Method not allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
defer http.Redirect(res, req, board.root, http.StatusSeeOther)
|
||||
|
||||
req.ParseForm()
|
||||
author := req.PostFormValue("author")
|
||||
content := req.PostFormValue("content")
|
||||
|
||||
board.posts = &Post {
|
||||
Author: author,
|
||||
Content: content,
|
||||
Next: board.posts,
|
||||
}
|
||||
}
|
||||
150
examples/board/template.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package main
|
||||
|
||||
const tem = `
|
||||
{{define "top"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.}}</title>
|
||||
<style>
|
||||
* {
|
||||
appearance: none;
|
||||
--webkit-appearance: none;
|
||||
font-family: monospace;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #b3b5b1;
|
||||
color: #272825;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
body > * {
|
||||
margin: auto;
|
||||
max-width: 40em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #a2a89d;
|
||||
border: 1px solid #8f938c;
|
||||
border-bottom-color: #7f847b;
|
||||
border-radius: 4px 4px 0 0;
|
||||
text-shadow: 1px 1px 1px #b8bfb0;
|
||||
color: #4c4f49;
|
||||
}
|
||||
|
||||
main {
|
||||
background-color: #abaea8;
|
||||
border: 1px solid #959b91;
|
||||
border-top-color: #a1a59e;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
h1 { font-size: 1.5em }
|
||||
|
||||
hr {
|
||||
margin: 1em 0;
|
||||
border: none;
|
||||
border-top: 1px solid #7f847b9e;
|
||||
border-bottom: 1px solid #56604f1c;
|
||||
}
|
||||
|
||||
.formRow {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
button, input, textarea {
|
||||
margin: 0;
|
||||
border: 1px solid #7f847b;
|
||||
background-color: #b3b5b1;
|
||||
padding: 0.25em 0.5em;
|
||||
box-shadow: 1px 1px 0 inset #56604f38;
|
||||
border-radius: 2px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
button:not(:active), input[type=submit]:not(:active) {
|
||||
box-shadow: -1px -1px 0 inset #56604f38;
|
||||
}
|
||||
|
||||
button:active, input[type=submit]:active {
|
||||
border-color: #757a71;
|
||||
padding-top: calc(0.25em + 1px);
|
||||
padding-right: calc(0.5em - 1px);
|
||||
padding-bottom: calc(0.25em - 1px);
|
||||
padding-left: calc(0.5em + 1px);
|
||||
}
|
||||
|
||||
.post {
|
||||
padding: 0.4em 0.5em;
|
||||
background-color: #a2a69f;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #8f938c;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.post .author {
|
||||
font-size: 0.7em;
|
||||
color: #5e625a;
|
||||
text-shadow: 1px 1px 0 #b3b5b1;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
*:last-child { margin-bottom: 0 }
|
||||
*:first-child { margin-top: 0 }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>{{.}}</h1>
|
||||
</header>
|
||||
<main>
|
||||
{{end}}
|
||||
|
||||
{{define "bottom"}}</main></body></html>{{end}}
|
||||
|
||||
{{define "post"}}
|
||||
<div class=post>
|
||||
<div class=author>{{.Author}}</div>
|
||||
<div class=content>{{.Content}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "postForm"}}
|
||||
<form method=POST action=actionPost>
|
||||
<div class=formRow>
|
||||
<input type=text name=author id=author placeholder="Name" required>
|
||||
</div>
|
||||
<div class=formRow>
|
||||
<textarea name=content id=content placeholder="Post content" required></textarea>
|
||||
</div>
|
||||
<div class=formRow>
|
||||
<input type=submit value="Post">
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
{{define "pageGeneric"}}
|
||||
{{template "top" .Title}}
|
||||
<p>{{.Content}}</p>
|
||||
{{template "bottom"}}
|
||||
{{end}}
|
||||
|
||||
{{define "pageHome"}}
|
||||
{{template "top" "Home"}}
|
||||
{{template "postForm"}}
|
||||
<hr>
|
||||
{{range .}}
|
||||
{{template "post" .}}
|
||||
{{end}}
|
||||
{{template "bottom"}}
|
||||
{{end}}
|
||||
`
|
||||
45
examples/gifs/main.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "log"
|
||||
import "net/http"
|
||||
import "crypto/tls"
|
||||
import "hnakra/service"
|
||||
|
||||
func main () {
|
||||
static := http.FileServer(http.Dir("./static"))
|
||||
|
||||
http.HandleFunc("/gifs/", gifs)
|
||||
http.Handle("/gifs/static/", http.StripPrefix("/gifs/static", static))
|
||||
|
||||
err := (&service.HTTP { Mount: service.Mount {
|
||||
Path: "/gifs/",
|
||||
Name: "Gifs",
|
||||
Description: "Serves a lot of big gifs on one page.",
|
||||
TLSConfig: &tls.Config { InsecureSkipVerify: true },
|
||||
}}).Run()
|
||||
|
||||
if err != nil {
|
||||
log.Println("XXX", err)
|
||||
}
|
||||
}
|
||||
|
||||
func gifs (res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Add("content-type", "text/html")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
|
||||
img := func (number int) {
|
||||
fmt.Fprintf(res, "<img src='static/%d.gif'>", number)
|
||||
}
|
||||
|
||||
fmt.Fprintln(res, "<!DOCTYPE html><html><head><title>Gifs</title></head><body>")
|
||||
img(0)
|
||||
img(1)
|
||||
img(2)
|
||||
img(3)
|
||||
img(4)
|
||||
img(5)
|
||||
img(6)
|
||||
img(7)
|
||||
fmt.Fprintln(res, "</body></html>")
|
||||
}
|
||||
BIN
examples/gifs/static/0.gif
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
examples/gifs/static/1.gif
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
examples/gifs/static/2.gif
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
examples/gifs/static/3.gif
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
examples/gifs/static/4.gif
Normal file
|
After Width: | Height: | Size: 608 KiB |
BIN
examples/gifs/static/5.gif
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
examples/gifs/static/6.gif
Normal file
|
After Width: | Height: | Size: 6.3 MiB |
BIN
examples/gifs/static/7.gif
Normal file
|
After Width: | Height: | Size: 955 KiB |
26
examples/hellorld/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import "log"
|
||||
import "net/http"
|
||||
import "crypto/tls"
|
||||
import "hnakra/service"
|
||||
|
||||
func main () {
|
||||
http.HandleFunc("/hello/", hellorld)
|
||||
err := (&service.HTTP { Mount: service.Mount {
|
||||
Path: "/hello/",
|
||||
Name: "Hellorld",
|
||||
Description: "A test service.",
|
||||
TLSConfig: &tls.Config { InsecureSkipVerify: true },
|
||||
}}).Run()
|
||||
|
||||
if err != nil {
|
||||
log.Println("XXX", err)
|
||||
}
|
||||
}
|
||||
|
||||
func hellorld (res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Add("content-type", "text/plain")
|
||||
res.WriteHeader(200)
|
||||
res.Write([]byte("Hellorld!\n"))
|
||||
}
|
||||
BIN
examples/image/fractal.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
26
examples/image/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import "log"
|
||||
import "net/http"
|
||||
import "crypto/tls"
|
||||
import "hnakra/service"
|
||||
|
||||
func main () {
|
||||
http.HandleFunc("/fractal.png", func (res http.ResponseWriter, req *http.Request) {
|
||||
// An interesting quirk relating to http.ServeFile: there needs
|
||||
// to be a one-to-one correspondance between pattern path and
|
||||
// file. This means you can't serve an image on a rooted
|
||||
// subtree. Otherwise it will randomly fail. I think this has to
|
||||
// do with how *something* is caching the file.
|
||||
http.ServeFile(res, req, "fractal.png")
|
||||
})
|
||||
|
||||
err := (&service.HTTP { Mount: service.Mount {
|
||||
Path: "/fractal.png",
|
||||
Name: "Image",
|
||||
Description: "Displays an image of a fractal.",
|
||||
TLSConfig: &tls.Config { InsecureSkipVerify: true },
|
||||
}}).Run()
|
||||
|
||||
if err != nil { log.Println("XXX", err) }
|
||||
}
|
||||
24
examples/kamikaze/main.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "log"
|
||||
import "net/http"
|
||||
import "crypto/tls"
|
||||
import "hnakra/service"
|
||||
|
||||
func main () {
|
||||
http.HandleFunc("/kamikaze/", hellorld)
|
||||
err := (&service.HTTP { Mount: service.Mount {
|
||||
Path: "/kamikaze/",
|
||||
Name: "Kamikaze",
|
||||
Description: "A service that abrupltly closes upon any request, for testing",
|
||||
TLSConfig: &tls.Config { InsecureSkipVerify: true },
|
||||
}}).Run()
|
||||
|
||||
if err != nil {
|
||||
log.Println("XXX", err)
|
||||
}
|
||||
}
|
||||
|
||||
func hellorld (res http.ResponseWriter, req *http.Request) {
|
||||
log.Fatal("Goodbye!")
|
||||
}
|
||||