Skip to content

Commit

Permalink
Add JSON request payload from file, fileinput refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
1buran committed Apr 15, 2024
1 parent 47b9456 commit efe9c32
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 23 deletions.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
# rHttp - REPL for HTTP
[![codecov](https://codecov.io/gh/1buran/rHttp/graph/badge.svg?token=20IW0GY8R9)](https://codecov.io/gh/1buran/rHttp)
[![goreportcard](https://goreportcard.com/badge/github.com/1buran/rHttp)](https://goreportcard.com/report/github.com/1buran/redmine)
![Main demo](https://i.imgur.com/6ao55dy.gif)
![Main demo](https://i.imgur.com/I0vIcFS.gif)

#### Responses with minified JSON
![JSON min](https://i.imgur.com/Ii6CzZK.gif)
![JSON min](https://i.imgur.com/FFrxom5.gif)

#### Edit JSON request payload
![Edit JSON Payload](https://i.imgur.com/VAdcP65.gif)
![Edit JSON Payload](https://i.imgur.com/08bJisW.gif)

#### Load JSON request payload from file
![Attach file](https://i.imgur.com/CtpGGvZ.gif)

#### Load session
![Load session](https://i.imgur.com/TQ3uKG3.gif)
![Load session](https://i.imgur.com/iPGhPGI.gif)

#### Redirects
![Redirects](https://i.imgur.com/Dm9XCJh.gif)
![Redirects](https://i.imgur.com/rRA4vVy.gif)

## Introduction

Expand All @@ -34,12 +37,12 @@ Currently implemented:
- Auto following the redirects
- Easy manipulation of request cookies, headers, params (query string) and form values
- Easy manipulation of JSON request payload (through the built-in mini editor)
- Load JSON request payload from file
- Automatic syntax highlighting of the body of http responses
- Auto format JSON responses (useful for inspection of minified responses)
- Save & load sessions (useful for complex request setup)

In progress:
- Load JSON request payload from file
- Load binary data of upload form from file
- Config file for change key bindings, default settings

Expand Down Expand Up @@ -123,6 +126,7 @@ vhs demo/json-min.tape
vhs demo/load-session.tape
vhs demo/redirects.tape
vhs demo/edit-json-payload.tape
vhs demo/attach-file.tape
```
### imgur

Expand All @@ -135,6 +139,7 @@ demo["json-min"]="JSON min"
demo["load-session"]="Load session"
demo["redirects"]="Redirects"
demo["edit-json-payload"]="Edit JSON Payload"
demo["attach-file"]="Attach file"
for i in ${!demo[@]}; do
. .env && url=`curl --location https://api.imgur.com/3/image \
Expand Down
35 changes: 35 additions & 0 deletions demo/attach-file.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Output demo/attach-file.gif

Set Shell "bash"
Set FontSize 16
Set Padding 2
Set Margin 2
Set Width 1280
Set Height 1024
Set Framerate 60
Set TypingSpeed 150ms
Set PlaybackSpeed 0.4
Set CursorBlink false

Require rhttp
Type "rhttp" Enter

Sleep 3
Ctrl + f
Copy "POST" Paste Enter
Ctrl + p
Copy "demo/payload.json"
Paste
Sleep 1
Enter 2
Backspace 9
Copy "reqres.in" Paste Enter
Copy "/api/users" Paste Enter
Enter 8 Space
Enter Space
Ctrl + g
Sleep 3
PageDown
Sleep 3
PageUp
Ctrl + q
17 changes: 17 additions & 0 deletions demo/payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Purchase": [
{
"Product": {
"id": 1,
"name": "Bubble Gum",
"cost": 7.52
},
"Seller": {
"id": 101,
"name": "Kurt",
"rating": 4.8,
"soldItems": 2145
}
}
]
}
12 changes: 10 additions & 2 deletions fileinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ func (f *FileInput) Focus() {
f.widget.Focus()
}

func (f *FileInput) Value() string {
return f.widget.Value()
}

func (f *FileInput) Reset() {
f.widget.Reset()
}

func (f *FileInput) Blur() {
f.widget.Blur()
}
Expand All @@ -79,14 +87,14 @@ func NewFileInput(id, mode int, title, placeholder string) FileInput {

type FileInputReader struct {
Id int
Reader io.Reader
Reader io.ReadCloser
Error error
Path string
}

type FileInputWriter struct {
Id int
Writer io.Writer
Writer io.WriteCloser
Error error
Path string
}
Expand Down
9 changes: 7 additions & 2 deletions keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (

type KeyMap struct {
Next, Prev, Quit, Help, Run, FullScreen, PageUp, PageDown, Up, Down, Enter,
Delete, Autocomplete, LoadSession, SaveSession, ToggleCheckbox, ToggleJSON, SaveJSON key.Binding
Delete, Autocomplete, LoadSession, SaveSession, ToggleCheckbox, ToggleJSON, SaveJSON,
Payload key.Binding
}

func (k KeyMap) ShortHelp() []key.Binding {
Expand All @@ -18,7 +19,7 @@ func (k KeyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.Next, k.Prev, k.Enter, k.Run, k.Delete, k.ToggleCheckbox},
{k.FullScreen, k.Help, k.Quit, k.LoadSession, k.SaveSession, k.Autocomplete},
{k.ToggleJSON, k.SaveJSON, k.PageDown, k.PageUp},
{k.ToggleJSON, k.SaveJSON, k.Payload, k.PageDown, k.PageUp},
}
}

Expand Down Expand Up @@ -87,6 +88,10 @@ var keys = KeyMap{
key.WithKeys("alt+enter"),
key.WithHelp("Alt+enter", "save JSON payload"),
),
Payload: key.NewBinding(
key.WithKeys("ctrl+p"),
key.WithHelp("Ctrl+p", "add payload"),
),
}

// Helper struct for linking together help and key bindings.
Expand Down
70 changes: 59 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const (
const (
sessionSave = end + iota + 1
sessionLoad
payload

fileInputsEnd
)
Expand All @@ -82,6 +83,7 @@ const (
nothing = iota
jsonPayload
formPayload
file
)

const (
Expand Down Expand Up @@ -159,16 +161,18 @@ func newReqest() (r *http.Request) {
}

// Prepare request before send.
// TODO consider refactoring/remove all this function to avoid using of global variables
func prepareRequest(r *http.Request, p int) {
switch p {
// case file: ? (see todo)
case formPayload:
sbar.Info("send form values")
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
r.Body = newReadCloser(formValues.Encode())
r.Body = io.NopCloser(strings.NewReader(formValues.Encode()))
case jsonPayload:
sbar.Info("send JSON payload")
r.Header.Set("Content-Type", "application/json")
r.Body = newReadCloser(jsonPayloadEncoded)
r.Body = io.NopCloser(strings.NewReader(jsonPayloadEncoded))
}
}

Expand Down Expand Up @@ -605,7 +609,9 @@ func initialModel() model {

f1 := NewFileInput(sessionSave, WriteMode, "Session save: ", "/home/user/ses.json")
f2 := NewFileInput(sessionLoad, ReadMode, "Session load: ", "/home/user/ses.json")
fileInputs = append(fileInputs, f1, f2)
f3 := NewFileInput(payload, ReadMode, "Payload: ", "/home/user/data.json")

fileInputs = append(fileInputs, f1, f2, f3)

txt := textarea.New()
txt.MaxHeight = 0
Expand Down Expand Up @@ -635,13 +641,6 @@ func (m model) Init() tea.Cmd {

var formValues = make(url.Values)

type readCloser struct {
strings.Reader
}

func (rc *readCloser) Close() error { return nil }
func newReadCloser(s string) *readCloser { return &readCloser{*strings.NewReader(s)} }

func eraseIfError(t textinput.Model) {
if t.Err != nil {
t.Reset()
Expand Down Expand Up @@ -737,7 +736,7 @@ func (m *model) checkboxHandler(msg tea.Msg, i int) (tea.Model, tea.Cmd) {
}

// Load session: create and populate request and response from the given file.
func loadSession(m model, r io.Reader) (tea.Model, tea.Cmd) {
func loadSession(m model, r io.ReadCloser) (tea.Model, tea.Cmd) {
ses, _ := NewSession(
m.req, m.res, sbar.GetReqCount(),
sbar.GetResTime(), formValues, m.resBodyLines)
Expand Down Expand Up @@ -788,7 +787,25 @@ func loadSession(m model, r io.Reader) (tea.Model, tea.Cmd) {
m.res.Proto = ses.Response.Proto
m.res.Header = ses.Response.Headers
m.resBodyLines = ses.Response.BodyLines
m.reqPayload = nothing

return m, nil
}

var filePayload string

// Load payload.
func loadPayload(m model, r io.ReadCloser, path string) (tea.Model, tea.Cmd) {
if strings.HasSuffix(path, ".json") {
m.req.Header.Set("Content-Type", "application/json")
}
m.req.Method = "POST"
m.inputs[method].SetValue("POST")
m.reqPayload = file

filePayload = path

m.req.Body = r
return m, nil
}

Expand All @@ -809,6 +826,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.focused = 0
m.focusPrompt(0)
return loadSession(m, msg.Reader)
case payload:
sbar.Info("add payload, read data from: " + msg.Path)
m.focused = 0
m.focusPrompt(0)
return loadPayload(m, msg.Reader, msg.Path)
}
case FileInputWriter:
if msg.Error != nil {
Expand Down Expand Up @@ -938,6 +960,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.focusPrompt(0)
}
// return m, nil
case key.Matches(msg, m.keys.Payload):
idx := fileinputIndex(payload)
if !m.fileInputs[idx].visible {
m.blurAllPrompts()
m.fileInputs[idx].SetVisible()
m.fileInputs[idx].Focus()
m.focused = payload
} else {
m.blurAllPrompts()
m.fileInputs[idx].Hide()
m.focused = 0
m.focusPrompt(0)
}
case key.Matches(msg, m.keys.Delete):
switch m.focused {
case header, headerVal:
Expand All @@ -953,6 +988,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.textArea.Reset()
m.reqPayload = nothing
m.req.Header.Del("Content-Type")
case payload:
sbar.Warning("remove req payload")
filePayload = ""
idx := fileinputIndex(payload)
m.fileInputs[idx].Reset()
m.reqPayload = nothing
m.req.Body = nil
m.req.Header.Del("Content-Type")
}
case key.Matches(msg, m.keys.ToggleCheckbox):
switch m.focused {
Expand Down Expand Up @@ -1000,6 +1043,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case sessionLoad:
idx := fileinputIndex(sessionLoad)
return m, m.fileInputs[idx].OpenFile()
case payload:
idx := fileinputIndex(payload)
return m, m.fileInputs[idx].OpenFile()
case jsonEditView:
var c tea.Cmd
m.textArea, c = m.textArea.Update(msg)
Expand Down Expand Up @@ -1083,6 +1129,8 @@ func (m model) View() string {
reqPayload = " " + bodyStyle.Render(formValues.Encode())
case jsonPayload:
reqPayload = " " + bodyStyle.Render(jsonPayloadEncoded)
case file:
reqPayload = " " + bodyStyle.Render(filePayload, " attached")
}

// print response
Expand Down
14 changes: 12 additions & 2 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func NewSession(rq *http.Request, rs *http.Response, rqc int, rqt string, rqf ma
}

// Save the session.
func (s *Session) Save(o io.Writer) error {
func (s *Session) Save(o io.WriteCloser) error {
b, err := json.Marshal(s)
if err != nil {
return err
Expand All @@ -76,17 +76,27 @@ func (s *Session) Save(o io.Writer) error {
return err
}

err = o.Close()
if err != nil {
return err
}

return nil
}

// Load the session.
func (s *Session) Load(r io.Reader) error {
func (s *Session) Load(r io.ReadCloser) error {

b, err := io.ReadAll(r)
if err != nil {
return err
}

err = r.Close()
if err != nil {
return err
}

err = json.Unmarshal(b, s)
if err != nil {
return err
Expand Down

0 comments on commit efe9c32

Please sign in to comment.