Start converting to use github.com/ian-kent/gotcha

This commit is contained in:
Ian Kent 2014-06-23 19:21:20 +01:00
parent bc56772c03
commit 5409809872
5 changed files with 114 additions and 270 deletions

View file

@ -11,6 +11,8 @@ release: release-deps
gox
deps:
go get github.com/ian-kent/gotcha/...
go get github.com/ian-kent/go-log/...
go get github.com/jteeuwen/go-bindata/...
go get labix.org/v2/mgo

View file

@ -4,28 +4,25 @@ import (
"log"
"encoding/json"
"fmt"
"net"
"net/http"
"net/smtp"
"strconv"
"bufio"
"strings"
"github.com/ian-kent/Go-MailHog/mailhog/data"
"github.com/ian-kent/Go-MailHog/mailhog/config"
"github.com/ian-kent/Go-MailHog/mailhog/storage"
"github.com/ian-kent/Go-MailHog/mailhog/http/router"
gotcha "github.com/ian-kent/gotcha/app"
"github.com/ian-kent/gotcha/http"
)
type APIv1 struct {
config *config.Config
exitChannel chan int
server *http.Server
eventlisteners []*EventListener
app *gotcha.App
}
type EventListener struct {
conn net.Conn
bufrw *bufio.ReadWriter
session *http.Session
ch chan []byte
}
type ReleaseConfig struct {
@ -34,25 +31,24 @@ type ReleaseConfig struct {
Port string
}
func CreateAPIv1(exitCh chan int, conf *config.Config, server *http.Server) *APIv1 {
func CreateAPIv1(conf *config.Config, app *gotcha.App) *APIv1 {
log.Println("Creating API v1")
apiv1 := &APIv1{
config: conf,
exitChannel: exitCh,
server: server,
eventlisteners: make([]*EventListener, 0),
app: app,
}
r := server.Handler.(*router.Router)
r := app.Router
r.Get("^/api/v1/messages/?$", apiv1.messages)
r.Delete("^/api/v1/messages/?$", apiv1.delete_all)
r.Get("^/api/v1/messages/([0-9a-f]+)/?$", apiv1.message)
r.Delete("^/api/v1/messages/([0-9a-f]+)/?$", apiv1.delete_one)
r.Get("^/api/v1/messages/([0-9a-f]+)/download/?$", apiv1.download)
r.Get("^/api/v1/messages/([0-9a-f]+)/mime/part/(\\d+)/download/?$", apiv1.download_part)
r.Post("^/api/v1/messages/([0-9a-f]+)/release/?$", apiv1.release_one)
r.Get("^/api/v1/events/?$", apiv1.eventstream)
r.Get("/api/v1/messages/?", apiv1.messages)
r.Delete("/api/v1/messages/?", apiv1.delete_all)
r.Get("/api/v1/messages/(?P<id>[0-9a-f]+)/?", apiv1.message)
r.Delete("/api/v1/messages/(?P<id>[0-9a-f]+)/?", apiv1.delete_one)
r.Get("/api/v1/messages/(?P<id>[0-9a-f]+)/download/?", apiv1.download)
r.Get("/api/v1/messages/(?P<id>[0-9a-f]+)/mime/part/(\\d+)/download/?", apiv1.download_part)
r.Post("/api/v1/messages/(?P<id>[0-9a-f]+)/release/?", apiv1.release_one)
r.Get("/api/v1/events/?", apiv1.eventstream)
go func() {
for {
@ -63,8 +59,6 @@ func CreateAPIv1(exitCh chan int, conf *config.Config, server *http.Server) *API
json := string(bytes)
log.Printf("Sending content: %s\n", json)
apiv1.broadcast(json)
case <- exitCh:
break;
}
}
}()
@ -75,7 +69,7 @@ func CreateAPIv1(exitCh chan int, conf *config.Config, server *http.Server) *API
func (apiv1 *APIv1) broadcast(json string) {
log.Println("[APIv1] BROADCAST /api/v1/events")
for _, l := range apiv1.eventlisteners {
log.Printf("Sending to connection: %s\n", l.conn.RemoteAddr())
log.Printf("Sending to connection: %s\n", l.session.Request.RemoteAddr)
lines := strings.Split(json, "\n")
data := ""
@ -85,49 +79,32 @@ func (apiv1 *APIv1) broadcast(json string) {
data += "\n"
size := fmt.Sprintf("%X", len(data) + 1)
l.bufrw.Write([]byte(size + "\r\n"))
l.ch <- []byte(size + "\r\n")
lines = strings.Split(data, "\n")
for _, ln := range lines {
l.bufrw.Write([]byte(ln + "\n"))
}
_, err := l.bufrw.Write([]byte("\r\n"))
if err != nil {
log.Printf("Error writing to connection: %s\n", err)
l.conn.Close()
// TODO remove from array
}
err = l.bufrw.Flush()
if err != nil {
log.Printf("Error flushing buffer: %s\n", err)
l.conn.Close()
// TODO remove from array
l.ch <- []byte(ln + "\n")
}
l.ch <- []byte("\r\n")
}
}
func (apiv1 *APIv1) eventstream(w http.ResponseWriter, r *http.Request, route *router.Route) {
func (apiv1 *APIv1) eventstream(session *http.Session) {
log.Println("[APIv1] GET /api/v1/events")
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Write([]byte("\n\n"))
hj, ok := w.(http.Hijacker)
if !ok {
log.Println("[APIv1] Connection hijack failed")
return
}
conn, bufrw, err := hj.Hijack()
if err != nil {
log.Println("[APIv1] Connection hijack failed")
return
}
apiv1.eventlisteners = append(apiv1.eventlisteners, &EventListener{conn, bufrw})
apiv1.eventlisteners = append(apiv1.eventlisteners, &EventListener{
session,
session.Response.Chunked(),
})
session.Response.Headers.Add("Content-Type", "text/event-stream")
session.Response.Headers.Add("Cache-Control", "no-cache")
session.Response.Headers.Add("Connection", "keep-alive")
session.Response.Write([]byte("\n\n"))
session.Response.Send()
}
func (apiv1 *APIv1) messages(w http.ResponseWriter, r *http.Request, route *router.Route) {
func (apiv1 *APIv1) messages(session *http.Session) {
log.Println("[APIv1] GET /api/v1/messages")
// TODO start, limit
@ -135,122 +112,118 @@ func (apiv1 *APIv1) messages(w http.ResponseWriter, r *http.Request, route *rout
case *storage.MongoDB:
messages, _ := apiv1.config.Storage.(*storage.MongoDB).List(0, 1000)
bytes, _ := json.Marshal(messages)
w.Header().Set("Content-Type", "text/json")
w.Write(bytes)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
case *storage.Memory:
messages, _ := apiv1.config.Storage.(*storage.Memory).List(0, 1000)
bytes, _ := json.Marshal(messages)
w.Header().Set("Content-Type", "text/json")
w.Write(bytes)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
default:
w.WriteHeader(500)
session.Response.Status = 500
}
}
func (apiv1 *APIv1) message(w http.ResponseWriter, r *http.Request, route *router.Route) {
match := route.Pattern.FindStringSubmatch(r.URL.Path)
id := match[1]
func (apiv1 *APIv1) message(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
bytes, _ := json.Marshal(message)
w.Header().Set("Content-Type", "text/json")
w.Write(bytes)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
case *storage.Memory:
message, _ := apiv1.config.Storage.(*storage.Memory).Load(id)
bytes, _ := json.Marshal(message)
w.Header().Set("Content-Type", "text/json")
w.Write(bytes)
session.Response.Headers.Add("Content-Type", "text/json")
session.Response.Write(bytes)
default:
w.WriteHeader(500)
session.Response.Status = 500
}
}
func (apiv1 *APIv1) download(w http.ResponseWriter, r *http.Request, route *router.Route) {
match := route.Pattern.FindStringSubmatch(r.URL.Path)
id := match[1]
log.Printf("[APIv1] GET /api/v1/messages/%s/download\n", id)
func (apiv1 *APIv1) download(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)
w.Header().Set("Content-Type", "message/rfc822")
w.Header().Set("Content-Disposition", "attachment; filename=\"" + id + ".eml\"")
session.Response.Headers.Add("Content-Type", "message/rfc822")
session.Response.Headers.Add("Content-Disposition", "attachment; filename=\"" + id + ".eml\"")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
for h, l := range message.Content.Headers {
for _, v := range l {
w.Write([]byte(h + ": " + v + "\r\n"))
session.Response.Write([]byte(h + ": " + v + "\r\n"))
}
}
w.Write([]byte("\r\n" + message.Content.Body))
session.Response.Write([]byte("\r\n" + message.Content.Body))
case *storage.Memory:
message, _ := apiv1.config.Storage.(*storage.Memory).Load(id)
for h, l := range message.Content.Headers {
for _, v := range l {
w.Write([]byte(h + ": " + v + "\r\n"))
session.Response.Write([]byte(h + ": " + v + "\r\n"))
}
}
w.Write([]byte("\r\n" + message.Content.Body))
session.Response.Write([]byte("\r\n" + message.Content.Body))
default:
w.WriteHeader(500)
session.Response.Status = 500
}
}
func (apiv1 *APIv1) download_part(w http.ResponseWriter, r *http.Request, route *router.Route) {
match := route.Pattern.FindStringSubmatch(r.URL.Path)
id := match[1]
part, _ := strconv.Atoi(match[2])
func (apiv1 *APIv1) download_part(session *http.Session) {
id := session.Stash["id"].(string)
part, _ := strconv.Atoi(session.Stash["part"].(string))
log.Printf("[APIv1] GET /api/v1/messages/%s/mime/part/%d/download\n", id, part)
// TODO extension from content-type?
w.Header().Set("Content-Disposition", "attachment; filename=\"" + id + "-part-" + match[2] + "\"")
session.Response.Headers.Add("Content-Disposition", "attachment; filename=\"" + id + "-part-" + strconv.Itoa(part) + "\"")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
for h, l := range message.MIME.Parts[part].Headers {
for _, v := range l {
w.Header().Set(h, v)
session.Response.Headers.Add(h, v)
}
}
w.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
case *storage.Memory:
message, _ := apiv1.config.Storage.(*storage.Memory).Load(id)
for h, l := range message.MIME.Parts[part].Headers {
for _, v := range l {
w.Header().Set(h, v)
session.Response.Headers.Add(h, v)
}
}
w.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
default:
w.WriteHeader(500)
session.Response.Status = 500
}
}
func (apiv1 *APIv1) delete_all(w http.ResponseWriter, r *http.Request, route *router.Route) {
func (apiv1 *APIv1) delete_all(session *http.Session) {
log.Println("[APIv1] POST /api/v1/messages")
w.Header().Set("Content-Type", "text/json")
session.Response.Headers.Add("Content-Type", "text/json")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
apiv1.config.Storage.(*storage.MongoDB).DeleteAll()
case *storage.Memory:
apiv1.config.Storage.(*storage.Memory).DeleteAll()
default:
w.WriteHeader(500)
session.Response.Status = 500
return
}
}
func (apiv1 *APIv1) release_one(w http.ResponseWriter, r *http.Request, route *router.Route) {
match := route.Pattern.FindStringSubmatch(r.URL.Path)
id := match[1]
func (apiv1 *APIv1) release_one(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] POST /api/v1/messages/%s/release\n", id)
w.Header().Set("Content-Type", "text/json")
session.Response.Headers.Add("Content-Type", "text/json")
var msg = &data.Message{}
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
@ -258,16 +231,17 @@ func (apiv1 *APIv1) release_one(w http.ResponseWriter, r *http.Request, route *r
case *storage.Memory:
msg, _ = apiv1.config.Storage.(*storage.Memory).Load(id)
default:
w.WriteHeader(500)
session.Response.Status = 500
return
}
decoder := json.NewDecoder(r.Body)
decoder := json.NewDecoder(session.Request.Body())
var cfg ReleaseConfig
err := decoder.Decode(&cfg)
if err != nil {
log.Printf("Error decoding request body: %s", err)
w.WriteHeader(500)
w.Write([]byte("Error decoding request body"))
session.Response.Status = 500
session.Response.Write([]byte("Error decoding request body"))
return
}
@ -285,24 +259,23 @@ func (apiv1 *APIv1) release_one(w http.ResponseWriter, r *http.Request, route *r
err = smtp.SendMail(cfg.Host + ":" + cfg.Port, nil, "nobody@" + apiv1.config.Hostname, []string{cfg.Email}, bytes)
if err != nil {
log.Printf("Failed to release message: %s", err)
w.WriteHeader(500)
session.Response.Status = 500
return
}
log.Printf("Message released successfully")
}
func (apiv1 *APIv1) delete_one(w http.ResponseWriter, r *http.Request, route *router.Route) {
match := route.Pattern.FindStringSubmatch(r.URL.Path)
id := match[1]
func (apiv1 *APIv1) delete_one(session *http.Session) {
id := session.Stash["id"].(string)
log.Printf("[APIv1] POST /api/v1/messages/%s/delete\n", id)
w.Header().Set("Content-Type", "text/json")
session.Response.Headers.Add("Content-Type", "text/json")
switch apiv1.config.Storage.(type) {
case *storage.MongoDB:
apiv1.config.Storage.(*storage.MongoDB).DeleteOne(id)
case *storage.Memory:
apiv1.config.Storage.(*storage.Memory).DeleteOne(id)
default:
w.WriteHeader(500)
session.Response.Status = 500
}
}

View file

@ -1,79 +0,0 @@
package router;
import (
"regexp"
"net/http"
)
// http://stackoverflow.com/questions/6564558/wildcards-in-the-pattern-for-http-handlefunc
type Route struct {
Methods map[string]int
Pattern *regexp.Regexp
Handler HandlerFunc
}
type HandlerFunc func(http.ResponseWriter, *http.Request, *Route)
//type Handler http.Handler
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, route *Route) {
f(w, r, route)
}
type Router struct {
routes []*Route
}
func (h *Router) Get(pattern string, handler HandlerFunc) {
h.Handler([]string{"GET"}, regexp.MustCompile(pattern), handler)
}
func (h *Router) Post(pattern string, handler HandlerFunc) {
h.Handler([]string{"POST"}, regexp.MustCompile(pattern), handler)
}
func (h *Router) Put(pattern string, handler HandlerFunc) {
h.Handler([]string{"PUT"}, regexp.MustCompile(pattern), handler)
}
func (h *Router) Delete(pattern string, handler HandlerFunc) {
h.Handler([]string{"DELETE"}, regexp.MustCompile(pattern), handler)
}
func (h *Router) Patch(pattern string, handler HandlerFunc) {
h.Handler([]string{"PATCH"}, regexp.MustCompile(pattern), handler)
}
func (h *Router) Options(pattern string, handler HandlerFunc) {
h.Handler([]string{"OPTIONS"}, regexp.MustCompile(pattern), handler)
}
func (h *Router) Handler(methods []string, pattern *regexp.Regexp, handler HandlerFunc) {
m := make(map[string]int,0)
for _, v := range methods {
m[v] = 1
}
h.routes = append(h.routes, &Route{m, pattern, handler})
}
func (h *Router) HandleFunc(methods []string, pattern *regexp.Regexp, handler func(http.ResponseWriter, *http.Request, *Route)) {
m := make(map[string]int,0)
for _, v := range methods {
m[v] = 1
}
h.routes = append(h.routes, &Route{m, pattern, HandlerFunc(handler)})
}
func (h *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, route := range h.routes {
if route.Pattern.MatchString(r.URL.Path) {
_, ok := route.Methods[r.Method]
if ok {
route.Handler.ServeHTTP(w, r, route)
return
}
}
}
// no pattern matched; send 404 response
http.NotFound(w, r)
}

View file

@ -1,86 +1,14 @@
package http
import (
"net/http"
"strings"
"log"
"github.com/ian-kent/Go-MailHog/mailhog/config"
"github.com/ian-kent/Go-MailHog/mailhog/http/api"
"github.com/ian-kent/Go-MailHog/mailhog/http/router"
"github.com/ian-kent/gotcha/http"
"html/template"
)
var exitChannel chan int
var cfg *config.Config
func Index(session *http.Session) {
html, _ := session.RenderTemplate("index.html")
// TODO clean this mess up
func web_exit(w http.ResponseWriter, r *http.Request, route *router.Route) {
web_headers(w)
w.Write([]byte("Exiting MailHog!"))
exitChannel <- 1
}
func web_index(w http.ResponseWriter, r *http.Request, route *router.Route) {
web_headers(w)
data, _ := cfg.Assets("assets/templates/index.html")
w.Write([]byte(web_render(string(data))))
}
func web_static(w http.ResponseWriter, r *http.Request, route *router.Route) {
match := route.Pattern.FindStringSubmatch(r.URL.Path)
file := match[1]
log.Printf("[HTTP] GET %s\n", file)
if strings.HasSuffix(file, ".gif") {
w.Header().Set("Content-Type", "image/gif")
} else if strings.HasSuffix(file, ".png") {
w.Header().Set("Content-Type", "image/png")
} else if strings.HasSuffix(file, ".js") {
w.Header().Set("Content-Type", "text/javascript")
} else {
w.Header().Set("Content-Type", "text/plain")
}
data, err := cfg.Assets("assets" + file)
if err != nil {
w.WriteHeader(404)
return
}
w.Write(data)
}
func web_render(content string) string {
data, _ := cfg.Assets("assets/templates/layout.html")
layout := string(data)
html := strings.Replace(layout, "<%= content %>", content, -1)
// TODO clean this up
html = strings.Replace(html, "<%= config[Hostname] %>", cfg.Hostname, -1)
return html
}
func web_headers(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/html")
}
func Start(exitCh chan int, conf *config.Config) {
exitChannel = exitCh
cfg = conf
r := &router.Router{}
server := &http.Server{
Addr: conf.HTTPBindAddr,
Handler: r,
}
r.Get("^/exit/?$", web_exit)
r.Get("^(/js/controllers.js)$", web_static)
r.Get("^(/images/hog.png)$", web_static)
r.Get("^(/images/github.png)$", web_static)
r.Get("^(/images/ajax-loader.gif)$", web_static)
r.Get("^/$", web_index)
api.CreateAPIv1(exitCh, conf, server)
server.ListenAndServe()
session.Stash["Page"] = "Browse"
session.Stash["Content"] = template.HTML(html)
session.Render("layout.html")
}

28
main.go
View file

@ -3,12 +3,14 @@ package main
import (
"flag"
"github.com/ian-kent/Go-MailHog/mailhog/config"
"github.com/ian-kent/Go-MailHog/mailhog/http"
"github.com/ian-kent/Go-MailHog/mailhog/smtp"
"github.com/ian-kent/Go-MailHog/mailhog/http/api"
"github.com/ian-kent/Go-MailHog/mailhog/storage"
"log"
gotcha "github.com/ian-kent/gotcha/app"
"github.com/ian-kent/go-log/log"
"net"
"os"
mhhttp "github.com/ian-kent/Go-MailHog/mailhog/http"
)
var conf *config.Config
@ -73,8 +75,26 @@ func main() {
}
func web_listen() {
log.Printf("[HTTP] Binding to address: %s\n", conf.HTTPBindAddr)
http.Start(exitCh, conf)
log.Info("[HTTP] Binding to address: %s", conf.HTTPBindAddr)
var app = gotcha.Create(Asset)
app.Config.Listen = conf.HTTPBindAddr
r := app.Router
r.Get("/images/(?P<file>.*)", r.Static("assets/images/{{file}}"))
r.Get("/js/(?P<file>.*)", r.Static("assets/js/{{file}}"))
r.Get("/", mhhttp.Index)
api.CreateAPIv1(conf, app)
app.Config.LeftDelim = ">>";
app.Config.RightDelim = "<<";
app.Start()
<-make(chan int)
exitCh<-1
}
func smtp_listen() *net.TCPListener {