├── README.md
├── banner.html
├── go.mod
├── go.sum
├── hero.png
├── main.go
└── motivation.jpg
/README.md:
--------------------------------------------------------------------------------
1 | # vibe-http
2 |
3 | 
4 |
5 | A tiny HTTP server that passes the HTTP request to OpenAI's ChatGPT API and returns the response.
6 |
7 | Consequentially, each website visitor will get a different experience. To limit the chaos a little
8 | bit, previous requests and responses are passed to ChatGPT as context.
9 |
10 | ## Demo
11 |
12 | Hosted at https://vibehttp.com/. Try it out! Be creative with your paths because the server gets the
13 | entire packet. For example, check out
14 |
15 | - https://vibehttp.com/kevin/what-is-the-meaning-of-life
16 | - https://vibehttp.com/hotels/nyc
17 | - https://vibehttp.com/cars/electric
18 |
19 | Try some subdomains too, for example
20 |
21 | - https://news.vibehttp.com/
22 | - https://jobs.vibehttp.com/
23 | - https://recipes.vibehttp.com/blog/winter-salads-for-dogs/
24 |
25 | Check out the _edgier_ parts of the site too
26 |
27 | - https://vibehttp.com/.htaccess
28 | - https://vibehttp.com/index.phps
29 | - https://vibehttp.com/robots.txt
30 |
31 | Don't like your page? Hit refresh to reroll.
32 |
33 | ## Motivation
34 |
35 | 
36 |
37 | ## Run it locally
38 |
39 | ```bash
40 | OPENAI_API_KEY=sk-... go run main.go
41 | ```
42 |
--------------------------------------------------------------------------------
/banner.html:
--------------------------------------------------------------------------------
1 |
12 |
What is this?
13 |
14 | This page is completely AI-generated (except for this banner). Click
15 |
here for more details.
16 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/kevmo314/vibe-http
2 |
3 | go 1.23.4
4 |
5 | require (
6 | github.com/google/uuid v1.6.0
7 | github.com/openai/openai-go v0.1.0-beta.2
8 | )
9 |
10 | require (
11 | github.com/tidwall/gjson v1.14.4 // indirect
12 | github.com/tidwall/match v1.1.1 // indirect
13 | github.com/tidwall/pretty v1.2.1 // indirect
14 | github.com/tidwall/sjson v1.2.5 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
3 | github.com/openai/openai-go v0.1.0-beta.2 h1:Ra5nCFkbEl9w+UJwAciC4kqnIBUCcJazhmMA0/YN894=
4 | github.com/openai/openai-go v0.1.0-beta.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
5 | github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
6 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
7 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
8 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
9 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
10 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
11 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
12 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
13 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
14 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
15 |
--------------------------------------------------------------------------------
/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevmo314/vibe-http/463d765176d15bf73587de0622c7355c04349e15/hero.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | _ "embed"
6 | "log"
7 | "net/http"
8 | "net/http/httputil"
9 | "os"
10 | "strings"
11 |
12 | "github.com/google/uuid"
13 | "github.com/openai/openai-go"
14 | )
15 |
16 | //go:embed banner.html
17 | var bannerHTML []byte
18 |
19 | func main() {
20 | client := openai.NewClient()
21 | sessions := map[string][]openai.ChatCompletionMessageParamUnion{}
22 |
23 | go http.ListenAndServe(":http", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24 | // upgrade to https
25 | http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently)
26 | }))
27 |
28 | if err := http.ListenAndServeTLS(":https",
29 | "/etc/letsencrypt/live/vibehttp.com/fullchain.pem",
30 | "/etc/letsencrypt/live/vibehttp.com/privkey.pem",
31 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32 | log.Printf("%s %s %s", r.Method, r.Host, r.URL.Path)
33 |
34 | if r.Host == "share.vibehttp.com" {
35 | id, err := uuid.Parse(r.URL.Path[1:])
36 | if err != nil {
37 | http.NotFound(w, r)
38 | return
39 | }
40 | // serve that file!
41 | f, err := os.Open("data/" + id.String())
42 | if err != nil {
43 | http.NotFound(w, r)
44 | return
45 | }
46 | defer f.Close()
47 | w.Header().Set("Content-Type", "text/html; charset=utf-8")
48 | w.WriteHeader(http.StatusOK)
49 | if _, err := f.WriteTo(w); err != nil {
50 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
51 | w.WriteHeader(http.StatusInternalServerError)
52 | w.Write([]byte(err.Error()))
53 | return
54 | }
55 | if _, err := w.Write(bytes.Replace(bannerHTML, []byte("{{STATE}}"), []byte(r.URL.Path[1:]), -1)); err != nil {
56 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
57 | w.WriteHeader(http.StatusInternalServerError)
58 | w.Write([]byte(err.Error()))
59 | return
60 | }
61 | return
62 | }
63 |
64 | if !strings.HasSuffix(r.Host, "vibehttp.com") {
65 | // too many bots
66 | http.NotFound(w, r)
67 | return
68 | }
69 |
70 | pkt, err := httputil.DumpRequest(r, true)
71 | if err != nil {
72 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
73 | w.WriteHeader(http.StatusInternalServerError)
74 | w.Write([]byte(err.Error()))
75 | return
76 | }
77 |
78 | completionIDCookie := r.CookiesNamed("completion-id")
79 |
80 | var session []openai.ChatCompletionMessageParamUnion
81 |
82 | if len(completionIDCookie) > 0 {
83 | session = sessions[completionIDCookie[0].Value]
84 | }
85 | if len(session) == 0 {
86 | session = []openai.ChatCompletionMessageParamUnion{
87 | openai.SystemMessage("You are an HTTP web server. The user will send you raw HTTP packets. Respond with only HTML, which will be sent to the user. Embed your CSS and JavaScript and be creative and verbose with your output, you want to make a good impression on the user and users love fancy effects. Subsequent GET and POST requests will also be sent to you so feel free to include links or forms. Do not include images and do not wrap your output in ``` tags."),
88 | }
89 | }
90 | session = append(session, openai.UserMessage(string(pkt)))
91 | state := uuid.NewString()
92 | http.SetCookie(w, &http.Cookie{
93 | Name: "completion-id",
94 | Value: state,
95 | })
96 |
97 | w.Header().Set("Content-Type", "text/html; charset=utf-8")
98 | w.WriteHeader(http.StatusOK)
99 |
100 | completion := client.Chat.Completions.NewStreaming(r.Context(), openai.ChatCompletionNewParams{
101 | Messages: session,
102 | Model: openai.ChatModelGPT4o,
103 | })
104 | acc := openai.ChatCompletionAccumulator{}
105 | for completion.Next() {
106 | chunk := completion.Current()
107 | acc.AddChunk(chunk)
108 | if _, err := w.Write([]byte(chunk.Choices[0].Delta.Content)); err != nil {
109 | break
110 | }
111 | }
112 | if _, err := w.Write(bytes.Replace(bannerHTML, []byte("{{STATE}}"), []byte(state), -1)); err != nil {
113 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
114 | w.WriteHeader(http.StatusInternalServerError)
115 | w.Write([]byte(err.Error()))
116 | return
117 | }
118 | if err := completion.Close(); err != nil {
119 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
120 | w.WriteHeader(http.StatusInternalServerError)
121 | w.Write([]byte(err.Error()))
122 | return
123 | }
124 | if len(acc.Choices) == 0 {
125 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
126 | w.WriteHeader(http.StatusInternalServerError)
127 | w.Write([]byte("no choices"))
128 | return
129 | }
130 |
131 | session = append(session, openai.AssistantMessage(acc.Choices[0].Message.Content))
132 | sessions[state] = session
133 |
134 | f, err := os.Create("data/" + state)
135 | if err != nil {
136 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
137 | w.WriteHeader(http.StatusInternalServerError)
138 | w.Write([]byte(err.Error()))
139 | return
140 | }
141 | defer f.Close()
142 |
143 | if _, err := f.Write([]byte(acc.Choices[0].Message.Content)); err != nil {
144 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
145 | w.WriteHeader(http.StatusInternalServerError)
146 | w.Write([]byte(err.Error()))
147 | return
148 | }
149 | })); err != nil {
150 | log.Fatal(err)
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/motivation.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevmo314/vibe-http/463d765176d15bf73587de0622c7355c04349e15/motivation.jpg
--------------------------------------------------------------------------------