forked from wgroeneveld/go-jamming
added logging, refactored in webmention struct, use async after validation
This commit is contained in:
parent
5f5c7c6f21
commit
95abdcc7f0
|
@ -8,4 +8,4 @@ jobs:
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.0'
|
go-version: '^1.16.0'
|
||||||
- run: go test ./...
|
- run: go test ./... -v
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type loggingResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// mimic ResponseWriter's WriteHeader to capture the code
|
||||||
|
func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||||
|
lrw.statusCode = code
|
||||||
|
lrw.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logWriter := &loggingResponseWriter{w, http.StatusOK}
|
||||||
|
next.ServeHTTP(logWriter, r)
|
||||||
|
log.Info().
|
||||||
|
Str("url", r.RequestURI).
|
||||||
|
Str("method", r.Method).
|
||||||
|
Int("status", logWriter.statusCode).
|
||||||
|
Msg("handled")
|
||||||
|
})
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
// stole ideas from https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html
|
// stole ideas from https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html
|
||||||
// not that contempt with passing conf, but can't create receivers on non-local types, and won't move specifics into package app
|
// not that contempt with passing conf, but can't create receivers on non-local types, and won't move specifics into package app
|
||||||
|
// https://blog.questionable.services/article/http-handler-error-handling-revisited/ is the better idea, but more work
|
||||||
func (s *server) routes() {
|
func (s *server) routes() {
|
||||||
s.router.HandleFunc("/", index.Handle(s.conf)).Methods("GET")
|
s.router.HandleFunc("/", index.Handle(s.conf)).Methods("GET")
|
||||||
s.router.HandleFunc("/pingback", pingback.Handle(s.conf)).Methods("POST")
|
s.router.HandleFunc("/pingback", pingback.Handle(s.conf)).Methods("POST")
|
||||||
|
@ -16,3 +17,4 @@ func (s *server) routes() {
|
||||||
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandleGet(s.conf))).Methods("GET")
|
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandleGet(s.conf))).Methods("GET")
|
||||||
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandlePut(s.conf))).Methods("PUT")
|
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandlePut(s.conf))).Methods("PUT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/wgroeneveld/go-jamming/common"
|
"github.com/wgroeneveld/go-jamming/common"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
|
@ -36,7 +36,8 @@ func Start() {
|
||||||
|
|
||||||
server.routes()
|
server.routes()
|
||||||
http.Handle("/", r)
|
http.Handle("/", r)
|
||||||
|
r.Use(loggingMiddleware)
|
||||||
|
|
||||||
fmt.Printf("Serving at port %d...\n", server.conf.Port)
|
log.Info().Int("port", server.conf.Port).Msg("Serving...")
|
||||||
http.ListenAndServe(":" + strconv.Itoa(server.conf.Port), nil)
|
http.ListenAndServe(":" + strconv.Itoa(server.conf.Port), nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,23 @@ func HandlePost(conf *common.Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
if !validate(r, r.Header, conf) {
|
if !validate(r, r.Header, conf) {
|
||||||
http.Error(w, "400 bad request", http.StatusBadRequest)
|
common.BadRequest(w)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("%+v\n", r.Header)
|
|
||||||
|
target := r.FormValue("target")
|
||||||
|
if !isValidTargetUrl(target) {
|
||||||
|
common.BadRequest(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wm := &webmention{
|
||||||
|
source: r.FormValue("source"),
|
||||||
|
target: target,
|
||||||
|
}
|
||||||
|
|
||||||
|
go wm.receive()
|
||||||
|
common.Accept(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
package webmention
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wgroeneveld/go-jamming/common"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type webmention struct {
|
||||||
|
source string
|
||||||
|
target string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *webmention) String() string {
|
||||||
|
return fmt.Sprintf("source: %s, target: %s", wm.source, wm.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *webmention) receive() {
|
||||||
|
log.Info().Str("webmention", wm.String()).Msg("OK: looks valid")
|
||||||
|
body, geterr := common.Get(wm.source)
|
||||||
|
|
||||||
|
if geterr != nil {
|
||||||
|
log.Warn().Str("source", wm.source).Msg(" ABORT: invalid url")
|
||||||
|
wm.deletePossibleOlderWebmention()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wm.processSourceBody(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *webmention) deletePossibleOlderWebmention() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *webmention) processSourceBody(body string) {
|
||||||
|
|
||||||
|
}
|
|
@ -3,8 +3,11 @@ package webmention
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/wgroeneveld/go-jamming/common"
|
"github.com/wgroeneveld/go-jamming/common"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isValidUrl(url string) bool {
|
func isValidUrl(url string) bool {
|
||||||
|
@ -21,6 +24,7 @@ func isValidDomain(url string, conf *common.Config) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// great, these are needed to do the structural typing for the tests...
|
||||||
type httpReq interface {
|
type httpReq interface {
|
||||||
FormValue(key string) string
|
FormValue(key string) string
|
||||||
}
|
}
|
||||||
|
@ -28,6 +32,15 @@ type httpHeader interface {
|
||||||
Get(key string) string
|
Get(key string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidTargetUrl(url string) bool {
|
||||||
|
_, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Str("target", url).Msg("Invalid target URL")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func validate(r httpReq, h httpHeader, conf *common.Config) bool {
|
func validate(r httpReq, h httpHeader, conf *common.Config) bool {
|
||||||
return h.Get("Content-Type") == "application/x-www-form-urlencoded" &&
|
return h.Get("Content-Type") == "application/x-www-form-urlencoded" &&
|
||||||
isValidUrl(r.FormValue("source")) &&
|
isValidUrl(r.FormValue("source")) &&
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BadRequest(w http.ResponseWriter) {
|
||||||
|
http.Error(w, "400 bad request", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Accept(w http.ResponseWriter) {
|
||||||
|
w.WriteHeader(202)
|
||||||
|
w.Write([]byte("Thanks, bro. Will send these webmentions soon, pinky swear!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// something like this? https://freshman.tech/snippets/go/http-response-to-string/
|
||||||
|
func Get(url string) (string, error) {
|
||||||
|
resp, geterr := http.Get(url)
|
||||||
|
if geterr != nil {
|
||||||
|
return "", geterr
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
|
return "", fmt.Errorf("Status code for %s is not OK (%d)", url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, readerr := ioutil.ReadAll(resp.Body)
|
||||||
|
if readerr != nil {
|
||||||
|
return "", readerr
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
5
go.mod
5
go.mod
|
@ -2,4 +2,7 @@ module github.com/wgroeneveld/go-jamming
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require github.com/gorilla/mux v1.8.0
|
require (
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/rs/zerolog v1.21.0
|
||||||
|
)
|
||||||
|
|
27
go.sum
27
go.sum
|
@ -1,2 +1,29 @@
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
|
||||||
|
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -10,38 +10,8 @@ dayjs.extend(utc)
|
||||||
|
|
||||||
const log = require('pino')()
|
const log = require('pino')()
|
||||||
|
|
||||||
function isValidUrl(url) {
|
|
||||||
return url !== undefined &&
|
|
||||||
(url.startsWith("http://") || url.startsWith("https://"))
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidDomain(url) {
|
|
||||||
return config.allowedWebmentionSources.some(domain => {
|
|
||||||
return url.indexOf(domain) !== -1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Remember, TARGET is own domain, SOURCE is the article to process
|
|
||||||
https://www.w3.org/TR/webmention/#sender-notifies-receiver
|
|
||||||
example:
|
|
||||||
POST /webmention-endpoint HTTP/1.1
|
|
||||||
Host: aaronpk.example
|
|
||||||
Content-Type: application/x-www-form-urlencoded
|
|
||||||
|
|
||||||
source=https://waterpigs.example/post-by-barnaby&
|
|
||||||
target=https://aaronpk.example/post-by-aaron
|
|
||||||
|
|
||||||
|
|
||||||
HTTP/1.1 202 Accepted
|
|
||||||
*/
|
|
||||||
function validate(request) {
|
function validate(request) {
|
||||||
return request.type === "application/x-www-form-urlencoded" &&
|
// DONE
|
||||||
request.body !== undefined &&
|
|
||||||
isValidUrl(request?.body?.source) &&
|
|
||||||
isValidUrl(request?.body?.target) &&
|
|
||||||
request?.body?.source !== request?.body?.target &&
|
|
||||||
isValidDomain(request?.body?.target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isValidTargetUrl(target) {
|
async function isValidTargetUrl(target) {
|
||||||
|
|
10
main.go
10
main.go
|
@ -2,9 +2,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/wgroeneveld/go-jamming/app"
|
"github.com/wgroeneveld/go-jamming/app"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||||
|
// TODO this should only be enabled in local mode. Fix with config?
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
|
log.Debug().Msg("Let's a go!")
|
||||||
app.Start()
|
app.Start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mainz() {
|
||||||
|
fmt.Println("Hello, playground")
|
||||||
|
resp, err := http.Get("https://brainbaking.com/notes")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err2 := ioutil.ReadAll(resp.Body)
|
||||||
|
if err2 != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("tis ditte")
|
||||||
|
fmt.Printf("%s", body)
|
||||||
|
}
|
Loading…
Reference in New Issue