initial commit
This commit is contained in:
commit
5e84ebf0b7
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/bin
|
||||||
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Go bot example
|
||||||
|
|
||||||
|
This directory contains an example of a simple Campfire bot written in Go.
|
||||||
|
|
||||||
|
This bot implments a single endpoint, `/trace`. You can message this endpoint
|
||||||
|
with a URL. The bot will make a `GET` request to that URL, and respond with some
|
||||||
|
timings about how long parts of that request took: DNS lookup, time to first
|
||||||
|
byte, and so on.
|
||||||
|
|
||||||
|
The functionality of the bot is basic. But this example shows how you can:
|
||||||
|
|
||||||
|
- Start an HTTP service to listen on a bot endpoint
|
||||||
|
- Parse the JSON from the message request
|
||||||
|
- Respond to that request with some HTML-formatted text
|
||||||
149
main.go
Normal file
149
main.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"bytes"
|
||||||
|
// "log"
|
||||||
|
"time"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotMessage struct {
|
||||||
|
User struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"user"`
|
||||||
|
|
||||||
|
Room struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"room"`
|
||||||
|
|
||||||
|
Message struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Body struct {
|
||||||
|
HTML string `json:"html"`
|
||||||
|
Plain string `json:"plain"`
|
||||||
|
} `json:"body"`
|
||||||
|
} `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", commandHandler)
|
||||||
|
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8096"
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(http.ListenAndServe(":"+port, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitFirstWord(input string) (firstWord string, restOfString string) {
|
||||||
|
words := strings.Fields(input)
|
||||||
|
if len(words) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
firstWord = words[0]
|
||||||
|
restOfString = strings.Join(words[1:], " ")
|
||||||
|
return strings.ToLower(firstWord), restOfString
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var msg BotMessage
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&msg)
|
||||||
|
if err != nil {
|
||||||
|
errorResponse(w, http.StatusBadRequest, "invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command, arguments := SplitFirstWord(msg.Message.Body.Plain)
|
||||||
|
switch command {
|
||||||
|
case "ping":
|
||||||
|
fmt.Println("ping command received")
|
||||||
|
fmt.Fprintln(w, "Pong!")
|
||||||
|
case "trace":
|
||||||
|
fmt.Println("trace command received")
|
||||||
|
var j = traceHandler(w, r, arguments, msg.User.Name)
|
||||||
|
fmt.Fprintln(w, j)
|
||||||
|
case "math":
|
||||||
|
fmt.Fprintln(w, runMath(arguments))
|
||||||
|
case "hi":
|
||||||
|
fmt.Fprintln(w, "Hello " + msg.User.Name + "! How are you doing today?")
|
||||||
|
default:
|
||||||
|
fmt.Fprintln(w, "Command is: " + command + " arguments are: " + arguments + ". Not sure what to do...")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMath(s string)(string){
|
||||||
|
cmd := exec.Command("qalc", s)
|
||||||
|
// cmd.Stdin = strings.NewReader("and old falcon")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// log.Warn(err)
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
// fmt.Printf("translated phrase: %q\n", out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func traceHandler(w http.ResponseWriter, r *http.Request, urlstring string, username string)(string) {
|
||||||
|
var msg BotMessage
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&msg)
|
||||||
|
|
||||||
|
uri, err := url.Parse(urlstring)
|
||||||
|
if err != nil || (uri.Scheme != "http" && uri.Scheme != "https") {
|
||||||
|
return "That doesn't look like a valid URL for to me to call"
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := timeRequest(username, uri)
|
||||||
|
if err != nil {
|
||||||
|
// fmt.Fprintf(w, "Failed to time the request (%s)", err)
|
||||||
|
// return
|
||||||
|
return "Failed to time the request " //+ err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Fprintln(w, response)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeRequest(username string, uri *url.URL) (string, error) {
|
||||||
|
trace, err := TraceRequest(uri)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := fmt.Sprintf("Hi %s! I've checked %s for you, and here's what I found:<br><br>\n", username, uri)
|
||||||
|
result += formatDuration("DNS lookup", trace.DNSStart, trace.DNSDone)
|
||||||
|
result += formatDuration("Connect", trace.ConnectStart, trace.ConnectDone)
|
||||||
|
result += formatDuration("TLS negotiation", trace.TLSStart, trace.TLSDone)
|
||||||
|
result += formatDuration("Sending headers", trace.ConnectionReady(), trace.WroteHeaders)
|
||||||
|
result += formatDuration("Time to first byte", trace.WroteHeaders, trace.FirstByte)
|
||||||
|
result += formatDuration("Time to last byte", trace.WroteHeaders, trace.AllDone)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorResponse(w http.ResponseWriter, status int, msg string) {
|
||||||
|
w.WriteHeader(status)
|
||||||
|
fmt.Fprintf(w, "Error: %s", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDuration(label string, start, end time.Time) string {
|
||||||
|
dur := end.Sub(start).String()
|
||||||
|
if start.IsZero() || end.IsZero() {
|
||||||
|
dur = "n/a"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: <strong>%s</strong><br>\n", label, dur)
|
||||||
|
}
|
||||||
23
package.nix
Normal file
23
package.nix
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{ pkgs, lib }:
|
||||||
|
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "sox";
|
||||||
|
version = "1.0";
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
buildInputs = [ pkgs.go pkgs.libqalculate ];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
# Nothing to build for a static site
|
||||||
|
# cp -r $src/* $out/var/www/my-static-site/
|
||||||
|
mkdir -p $out/bin
|
||||||
|
go build -o $out/bin/ $src/
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
chmod +x $out/bin/sox
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
72
request_trace.go
Normal file
72
request_trace.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestTrace struct {
|
||||||
|
Started time.Time
|
||||||
|
DNSStart time.Time
|
||||||
|
DNSDone time.Time
|
||||||
|
ConnectStart time.Time
|
||||||
|
ConnectDone time.Time
|
||||||
|
TLSStart time.Time
|
||||||
|
TLSDone time.Time
|
||||||
|
WroteHeaders time.Time
|
||||||
|
FirstByte time.Time
|
||||||
|
AllDone time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t RequestTrace) ConnectionReady() time.Time {
|
||||||
|
if t.TLSDone.After(t.ConnectDone) {
|
||||||
|
return t.TLSDone
|
||||||
|
}
|
||||||
|
return t.ConnectDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TraceRequest(uri *url.URL) (RequestTrace, error) {
|
||||||
|
var trace RequestTrace
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
|
||||||
|
GotConn: func(info httptrace.GotConnInfo) { trace.Started = time.Now() },
|
||||||
|
DNSStart: func(info httptrace.DNSStartInfo) { trace.DNSStart = time.Now() },
|
||||||
|
DNSDone: func(info httptrace.DNSDoneInfo) { trace.DNSDone = time.Now() },
|
||||||
|
ConnectStart: func(network, addr string) { trace.ConnectStart = time.Now() },
|
||||||
|
ConnectDone: func(network, addr string, err error) { trace.ConnectDone = time.Now() },
|
||||||
|
TLSHandshakeStart: func() { trace.TLSStart = time.Now() },
|
||||||
|
TLSHandshakeDone: func(tls.ConnectionState, error) { trace.TLSDone = time.Now() },
|
||||||
|
WroteHeaders: func() { trace.WroteHeaders = time.Now() },
|
||||||
|
GotFirstResponseByte: func() { trace.FirstByte = time.Now() },
|
||||||
|
})
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", uri.String(), nil)
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return trace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
t.DisableKeepAlives = true
|
||||||
|
c := &http.Client{Transport: t}
|
||||||
|
|
||||||
|
resp, err := c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return trace, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
io.Copy(io.Discard, resp.Body)
|
||||||
|
trace.AllDone = time.Now()
|
||||||
|
|
||||||
|
return trace, nil
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user