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