diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..895b7205826d4353e00061e7489f82f5bcc7ba1c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# go-httpbin
+
+A WIP golang port of https://httpbin.org/.
+
+## Testing
+
+```
+go test
+go test -cover
+go test -coverprofile=cover.out && go tool cover -html=cover.out
+```
+
+## Running
+
+```
+go build && ./go-httpbin
+```
diff --git a/main.go b/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..288e276df4fbd40fd5a2e78446e186711622921c
--- /dev/null
+++ b/main.go
@@ -0,0 +1,96 @@
+package main
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"net/url"
+)
+
+// Resp is the standard JSON response from httpbin
+type Resp struct {
+	Args    url.Values  `json:"args"`
+	Headers http.Header `json:"headers"`
+	Origin  string      `json:"origin"`
+	URL     string      `json:"url"`
+
+	Data  string              `json:"data,omitempty"`
+	Files map[string][]string `json:"files,omitempty"`
+	Form  map[string][]string `json:"form,omitempty"`
+	JSON  map[string][]string `json:"json,omitempty"`
+}
+
+func getOrigin(r *http.Request) string {
+	origin := r.Header.Get("X-Forwarded-For")
+	if origin == "" {
+		origin = r.RemoteAddr
+	}
+	return origin
+}
+
+func getURL(r *http.Request) string {
+	scheme := r.Header.Get("X-Forwarded-Proto")
+	if scheme == "" {
+		scheme = r.Header.Get("X-Forwarded-Protocol")
+	}
+	if scheme == "" && r.Header.Get("X-Forwarded-Ssl") == "on" {
+		scheme = "https"
+	}
+	if scheme == "" {
+		scheme = "http"
+	}
+
+	host := r.URL.Host
+	if host == "" {
+		host = r.Host
+	}
+
+	u := &url.URL{
+		Scheme:     scheme,
+		Opaque:     r.URL.Opaque,
+		User:       r.URL.User,
+		Host:       host,
+		Path:       r.URL.Path,
+		RawPath:    r.URL.RawPath,
+		ForceQuery: r.URL.ForceQuery,
+		RawQuery:   r.URL.RawQuery,
+		Fragment:   r.URL.Fragment,
+	}
+	return u.String()
+}
+
+func writeResponse(w http.ResponseWriter, r *http.Request, resp *Resp) {
+	resp.Origin = getOrigin(r)
+	resp.URL = getURL(r)
+
+	body, err := json.Marshal(resp)
+	if err != nil {
+		log.Printf("error marshalling %v as JSON: %s", resp, err)
+	}
+	w.Write(body)
+}
+
+func logger(h http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		h.ServeHTTP(w, r)
+		log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL)
+	})
+}
+
+func get(w http.ResponseWriter, r *http.Request) {
+	r.ParseForm()
+	resp := &Resp{
+		Args:    r.Form,
+		Headers: r.Header,
+	}
+	writeResponse(w, r, resp)
+}
+
+func main() {
+	h := http.NewServeMux()
+	h.HandleFunc("/get", get)
+
+	log.Printf("listening on 9999")
+	err := http.ListenAndServe(":9999", logger(h))
+	log.Fatal(err)
+}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2068b3d8a4c78ab395e7a1b59d999604e084fba3
--- /dev/null
+++ b/main_test.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func TestGet(t *testing.T) {
+	r, _ := http.NewRequest("GET", "/get", nil)
+	r.Host = "localhost"
+	r.Header.Set("User-Agent", "test")
+	w := httptest.NewRecorder()
+	get(w, r)
+
+	if w.Code != 200 {
+		t.Fatalf("expected status code 200, got %d", w.Code)
+	}
+
+	var resp *Resp
+	err := json.Unmarshal(w.Body.Bytes(), &resp)
+	if err != nil {
+		t.Fatalf("failed to unmarshal body %s from JSON: %s", w.Body, err)
+	}
+
+	if resp.Args.Encode() != "" {
+		t.Fatalf("expected empty args, got %s", resp.Args.Encode())
+	}
+	if resp.Origin != "" {
+		t.Fatalf("expected empty origin, got %#v", resp.Origin)
+	}
+	if resp.URL != "http://localhost/get" {
+		t.Fatalf("unexpected url: %#v", resp.URL)
+	}
+
+	var headerTests = []struct {
+		key      string
+		expected string
+	}{
+		{"Content-Type", ""},
+		{"User-Agent", "test"},
+	}
+	for _, test := range headerTests {
+		if resp.Headers.Get(test.key) != test.expected {
+			t.Fatalf("expected %s = %#v, got %#v", test.key, test.expected, resp.Headers.Get(test.key))
+		}
+	}
+}