1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path"
"strings"
prompt "github.com/c-bata/go-prompt"
)
type RequestContext struct {
url *url.URL
header http.Header
client *http.Client
}
var ctx *RequestContext
// See https://github.com/eliangcs/http-prompt/blob/master/http_prompt/completion.py
var suggestions = []prompt.Suggest{
// Command
{"cd", "Change URL/path"},
{"exit", "Exit http-prompt"},
// HTTP Method
{"delete", "DELETE request"},
{"get", "GET request"},
{"patch", "GET request"},
{"post", "POST request"},
{"put", "PUT request"},
// HTTP Header
{"Accept", "Acceptable response media type"},
{"Accept-Charset", "Acceptable response charsets"},
{"Accept-Encoding", "Acceptable response content codings"},
{"Accept-Language", "Preferred natural languages in response"},
{"ALPN", "Application-layer protocol negotiation to use"},
{"Alt-Used", "Alternative host in use"},
{"Authorization", "Authentication information"},
{"Cache-Control", "Directives for caches"},
{"Connection", "Connection options"},
{"Content-Encoding", "Content codings"},
{"Content-Language", "Natural languages for content"},
{"Content-Length", "Anticipated size for payload body"},
{"Content-Location", "Where content was obtained"},
{"Content-MD5", "Base64-encoded MD5 sum of content"},
{"Content-Type", "Content media type"},
{"Cookie", "Stored cookies"},
{"Date", "Datetime when message was originated"},
{"Depth", "Applied only to resource or its members"},
{"DNT", "Do not track user"},
{"Expect", "Expected behaviors supported by server"},
{"Forwarded", "Proxies involved"},
{"From", "Sender email address"},
{"Host", "Target URI"},
{"HTTP2-Settings", "HTTP/2 connection parameters"},
{"If", "Request condition on state tokens and ETags"},
{"If-Match", "Request condition on target resource"},
{"If-Modified-Since", "Request condition on modification date"},
{"If-None-Match", "Request condition on target resource"},
{"If-Range", "Request condition on Range"},
{"If-Schedule-Tag-Match", "Request condition on Schedule-Tag"},
{"If-Unmodified-Since", "Request condition on modification date"},
{"Max-Forwards", "Max number of times forwarded by proxies"},
{"MIME-Version", "Version of MIME protocol"},
{"Origin", "Origin(s} issuing the request"},
{"Pragma", "Implementation-specific directives"},
{"Prefer", "Preferred server behaviors"},
{"Proxy-Authorization", "Proxy authorization credentials"},
{"Proxy-Connection", "Proxy connection options"},
{"Range", "Request transfer of only part of data"},
{"Referer", "Previous web page"},
{"TE", "Transfer codings willing to accept"},
{"Transfer-Encoding", "Transfer codings applied to payload body"},
{"Upgrade", "Invite server to upgrade to another protocol"},
{"User-Agent", "User agent string"},
{"Via", "Intermediate proxies"},
{"Warning", "Possible incorrectness with payload body"},
{"WWW-Authenticate", "Authentication scheme"},
{"X-Csrf-Token", "Prevent cross-site request forgery"},
{"X-CSRFToken", "Prevent cross-site request forgery"},
{"X-Forwarded-For", "Originating client IP address"},
{"X-Forwarded-Host", "Original host requested by client"},
{"X-Forwarded-Proto", "Originating protocol"},
{"X-Http-Method-Override", "Request method override"},
{"X-Requested-With", "Used to identify Ajax requests"},
{"X-XSRF-TOKEN", "Prevent cross-site request forgery"},
}
func livePrefix() (string, bool) {
if ctx.url.Path == "/" {
return "", false
}
return ctx.url.String() + "> ", true
}
func executor(in string) {
in = strings.TrimSpace(in)
var method, body string
blocks := strings.Split(in, " ")
switch blocks[0] {
case "exit":
fmt.Println("Bye!")
os.Exit(0)
case "cd":
if len(blocks) < 2 {
ctx.url.Path = "/"
} else {
ctx.url.Path = path.Join(ctx.url.Path, blocks[1])
}
return
case "get", "delete":
method = strings.ToUpper(blocks[0])
case "post", "put", "patch":
if len(blocks) < 2 {
fmt.Println("please set request body.")
return
}
body = strings.Join(blocks[1:], " ")
method = strings.ToUpper(blocks[0])
}
if method != "" {
req, err := http.NewRequest(method, ctx.url.String(), strings.NewReader(body))
if err != nil {
fmt.Println("err: " + err.Error())
return
}
req.Header = ctx.header
res, err := ctx.client.Do(req)
if err != nil {
fmt.Println("err: " + err.Error())
return
}
result, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("err: " + err.Error())
return
}
fmt.Printf("%s\n", result)
ctx.header = http.Header{}
return
}
if h := strings.Split(in, ":"); len(h) == 2 {
// Handling HTTP Header
ctx.header.Add(strings.TrimSpace(h[0]), strings.Trim(h[1], ` '"`))
} else {
fmt.Println("Sorry, I don't understand.")
}
}
func completer(in prompt.Document) []prompt.Suggest {
w := in.GetWordBeforeCursor()
if w == "" {
return []prompt.Suggest{}
}
return prompt.FilterHasPrefix(suggestions, w, true)
}
func main() {
var baseURL = "http://localhost:8000/"
if len(os.Args) == 2 {
baseURL = os.Args[1]
if strings.HasSuffix(baseURL, "/") {
baseURL += "/"
}
}
u, err := url.Parse(baseURL)
if err != nil {
log.Fatal(err)
}
ctx = &RequestContext{
url: u,
header: http.Header{},
client: &http.Client{},
}
p := prompt.New(
executor,
completer,
prompt.OptionPrefix(u.String()+"> "),
prompt.OptionLivePrefix(livePrefix),
prompt.OptionTitle("http-prompt"),
)
p.Run()
}
|