├── README.md ├── banner.html ├── go.mod ├── go.sum ├── hero.png ├── main.go └── motivation.jpg /README.md: -------------------------------------------------------------------------------- 1 | # vibe-http 2 | 3 | ![hero](hero.png) 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 | ![motivation](motivation.jpg) 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 |
18 | Permalink to this page 19 |
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 --------------------------------------------------------------------------------