├── samples
├── simple_get.http-twiddle
├── cookie.http-twiddle
└── simple_post.http-twiddle
├── README.md
└── http-twiddle.el
/samples/simple_get.http-twiddle:
--------------------------------------------------------------------------------
1 | GET / HTTP/1.1
2 | Host: localhost
3 |
4 |
--------------------------------------------------------------------------------
/samples/cookie.http-twiddle:
--------------------------------------------------------------------------------
1 | GET / HTTP/1.1
2 | Host: localhost
3 | Cookie: sessionid=93eb570c0cf1je2135ee35f9656d691c
4 |
5 |
--------------------------------------------------------------------------------
/samples/simple_post.http-twiddle:
--------------------------------------------------------------------------------
1 | POST / HTTP/1.1
2 | Host: localhost
3 | Content-Type: text/xml
4 | Connection: close
5 |
6 | Bobbob@example.com
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # http-twiddle
3 |
4 | This Emacs addon is useful for interactive testing and debugging hand-written HTTP requests:
5 |
6 | 1. Activate `http-twiddle-mode` with `M-x`.
7 | 2. Write your request.
8 | 3. `C-c C-c` (re)sends the request.
9 |
10 | (`C-u C-c C-c` to change the destination or port.)
11 |
12 | The request can either be written from scratch or you can paste it
13 | from a snoop/tcpdump and then twiddle from there.
14 |
15 | The mode will also activate automatically when opening a filename ending with `.http-twiddle`.
16 |
17 | ## Examples
18 |
19 | Try `M-x http-twiddle-mode-demo` in Emacs for a simple get-started example.
20 |
21 | If the Content-Length header is not written out (like in the examples below) it'll be added automatically on send.
22 |
23 | ### GET:
24 |
25 | GET /user/bob/ HTTP/1.1
26 | Host: example.com
27 | [blank line here]
28 |
29 | ### POST:
30 |
31 | POST /user/create/ HTTP/1.1
32 | Host: example.com
33 | Content-Type: text/xml
34 | Connection: close
35 |
36 | Bobbob@example.com
37 |
38 |
39 | ## About
40 |
41 | Version 1.0 was written by Luke Gorrie in February 2006 and released to the public domain.
42 |
43 | Extended and maintained by Hasan Veldstra since September 2008.
44 |
45 | Homepage:
46 |
--------------------------------------------------------------------------------
/http-twiddle.el:
--------------------------------------------------------------------------------
1 | ;;; http-twiddle.el -- send & twiddle & resend HTTP requests
2 |
3 | ;; This program belongs to the public domain.
4 |
5 | ;; Author: Luke Gorrie
6 | ;; Maintainer: Hasan Veldstra
7 | ;; Created: 1 Feb 2006
8 | ;; Adapted-By: Hasan Veldstra
9 | ;; Version: 1.0
10 | ;; URL: https://github.com/hassy/http-twiddle/blob/master/http-twiddle.el
11 | ;; Keywords: HTTP, REST, SOAP
12 |
13 | ;;; Commentary:
14 | ;;
15 | ;; This is a program for testing hand-written HTTP requests. You write
16 | ;; your request in an Emacs buffer (using http-twiddle-mode) and then
17 | ;; press `C-c C-c' each time you want to try sending it to the server.
18 | ;; This way you can interactively debug the requests. To change port or
19 | ;; destination do `C-u C-c C-c'.
20 | ;;
21 | ;; The program is particularly intended for the POST-"500 internal
22 | ;; server error"-edit-POST loop of integration with SOAP programs.
23 | ;;
24 | ;; The mode is activated by `M-x http-twiddle-mode' or automatically
25 | ;; when opening a filename ending with .http-twiddle.
26 | ;;
27 | ;; The request can either be written from scratch or you can paste it
28 | ;; from a snoop/tcpdump and then twiddle from there.
29 | ;;
30 | ;; See the documentation for the `http-twiddle-mode' and
31 | ;; `http-twiddle-mode-send' functions below for more details and try
32 | ;; `M-x http-twiddle-mode-demo' for a simple get-started example.
33 | ;;
34 | ;; Tested with GNU Emacs 21.4.1 and not tested/ported on XEmacs yet.
35 | ;;
36 | ;; Example buffer:
37 | ;;
38 | ;; POST / HTTP/1.0
39 | ;; Connection: close
40 | ;; Content-Length: $Content-Length
41 | ;;
42 | ;;
43 |
44 | ;;; Code:
45 |
46 | (require 'font-lock) ; faces
47 |
48 | (defgroup http-twiddle nil
49 | "HTTP Request Twiddling"
50 | :prefix "http-twiddle"
51 | :group 'communication)
52 |
53 | (eval-when-compile
54 | (unless (fboundp 'define-minor-mode)
55 | (require 'easy-mmode)
56 | (defalias 'define-minor-mode 'easy-mmode-define-minor-mode))
57 | (require 'cl))
58 |
59 | (define-minor-mode http-twiddle-mode
60 | "Minor mode for twiddling around with HTTP requests and sending them.
61 | Use `http-twiddle-mode-send' (\\[http-twiddle-mode-send]) to send the request."
62 | nil
63 | " http-twiddle"
64 | '(("\C-c\C-c" . http-twiddle-mode-send)))
65 |
66 | (defcustom http-twiddle-show-request t
67 | "*Show the request in the transcript."
68 | :type '(boolean)
69 | :group 'http-twiddle)
70 |
71 | (add-to-list 'auto-mode-alist '("\\.http-twiddle$" . http-twiddle-mode))
72 |
73 | (defvar http-twiddle-endpoint nil
74 | "Cache of the (HOST PORT) to send the request to.")
75 |
76 | (defvar http-twiddle-process nil
77 | "Socket connected to the webserver.")
78 |
79 | (defvar http-twiddle-port-history '()
80 | "History of port arguments entered in the minibuffer.
81 | \(To make XEmacs happy.)")
82 |
83 | (defvar http-twiddle-host-history '()
84 | "History of port arguments entered in the minibuffer.
85 | \(To make XEmacs happy.)")
86 |
87 | (defconst http-twiddle-font-lock-keywords
88 | (list
89 | '("^X-[a-zA-Z0-9-]+:" . font-lock--face)
90 | '("^[a-zA-Z0-9-]+:" . font-lock-keyword-face)
91 | '("HTTP/1.[01] [45][0-9][0-9] .*" . font-lock-warning-face)
92 | '("HTTP/1.[01] [23][0-9][0-9] .*" . font-lock-type-face)
93 | '("HTTP/1.[01]
?$" . font-lock-constant-face)
94 | (cons (regexp-opt '("GET" "POST" "HEAD" "PUT" "DELETE" "TRACE" "CONNECT"))
95 | font-lock-constant-face))
96 | "Keywords to highlight in http-twiddle-response-mode.")
97 |
98 | (defconst http-twiddle-response-mode-map
99 | (make-sparse-keymap)
100 | "Keymap for http-twiddle-response-mode.")
101 |
102 | (define-generic-mode http-twiddle-response-mode
103 | nil nil http-twiddle-font-lock-keywords
104 | nil
105 | '((lambda ()
106 | (use-local-map http-twiddle-response-mode-map)))
107 | "Major mode for interacting with HTTP responses.")
108 |
109 | (defun http-twiddle-mode-send (host port)
110 | "Send the current buffer to the server.
111 | Linebreaks are automatically converted to CRLF (\\r\\n) format and any
112 | occurences of \"$Content-Length\" are replaced with the actual content
113 | length. Any elisp code between $|...code...| is evaluated and the match
114 | is substituted with the evaluated value formatted as string."
115 | (interactive (http-twiddle-read-endpoint))
116 | ;; close any old connection
117 | (when (and http-twiddle-process
118 | (buffer-live-p (process-buffer http-twiddle-process)))
119 | (with-current-buffer (process-buffer http-twiddle-process)
120 | (let ((inhibit-read-only t))
121 | (widen)
122 | (delete-region (point-min) (point-max)))))
123 |
124 | (let ((content (buffer-string)))
125 | (with-temp-buffer
126 | (set (make-variable-buffer-local 'font-lock-keywords)
127 | http-twiddle-font-lock-keywords)
128 | (insert content)
129 | (http-twiddle-expand-template)
130 | (http-twiddle-convert-cr-to-crlf)
131 | (http-twiddle-expand-content-length)
132 | (let ((request (buffer-string))
133 | (inhibit-read-only t))
134 | (setq http-twiddle-process
135 | (open-network-stream "http-twiddle" "*HTTP Twiddle*" host port))
136 | (set-process-filter http-twiddle-process 'http-twiddle-process-filter)
137 | (set-process-sentinel http-twiddle-process 'http-twiddle-process-sentinel)
138 | (process-send-string http-twiddle-process request)
139 | (save-selected-window
140 | (pop-to-buffer (process-buffer http-twiddle-process))
141 | (unless (eq major-mode 'http-twiddle-response-mode)
142 | (http-twiddle-response-mode))
143 | (setq buffer-read-only t)
144 | (let ((inhibit-read-only t))
145 | (when http-twiddle-show-request
146 | (insert request)
147 | (set-window-start (selected-window) (point))))
148 | (set-mark (point)))))))
149 |
150 | (defun http-twiddle-read-endpoint ()
151 | "Return the endpoint (HOST PORT) to send the request to.
152 | Uses values specified in Host header, or prompts if it's not written out."
153 |
154 | (let ((rx "\\(^Host: \\)\\([^\r\n]+\\)")
155 | (str (buffer-string)))
156 | (if (null (string-match rx str))
157 | ;; ask
158 | (setq http-twiddle-endpoint
159 | (list (read-string "Host: (default localhost) "
160 | nil 'http-twiddle-host-history "localhost")
161 | (let ((input (read-from-minibuffer "Port: " nil nil t 'http-twiddle-port-history)))
162 | (if (integerp input)
163 | input
164 | (error "Not an integer: %S" input)))))
165 | ;; try to parse headers
166 | (let ((tokens (split-string (match-string 2 str) ":")))
167 | (if (= (length tokens) 1)
168 | (list (car tokens) 80)
169 | (list (car tokens) (string-to-number (car (cdr tokens)))))))))
170 |
171 | (defun http-twiddle-convert-cr-to-crlf ()
172 | "Convert \\n linebreaks to \\r\\n in the whole buffer."
173 | (save-excursion
174 | (goto-char (point-min))
175 | (while (re-search-forward "[^\r]\n" nil t)
176 | (backward-char)
177 | (insert "\r"))))
178 |
179 | (defun http-twiddle-expand-content-length ()
180 | "Replace any occurences of $Content-Length with the actual Content-Length. Insert one if needed."
181 | (save-excursion
182 | (goto-char (point-min))
183 | (let ((content-length
184 | (save-excursion (when (search-forward "\r\n\r\n" nil t)
185 | (- (point-max) (point))))))
186 |
187 | (let ((got-content-length-already
188 | (save-excursion
189 | (goto-char (point-min))
190 | (let ((case-fold-search t))
191 | (when (search-forward "content-length" (- (point-max) content-length 2) t)
192 | t)))))
193 |
194 | (unless got-content-length-already
195 | (save-excursion
196 | (goto-char (- (point-max) content-length 2))
197 | (insert "Content-Length: $Content-Length\r\n")))
198 |
199 | (unless (null content-length)
200 | (let ((case-fold-search t))
201 | (while (search-forward "$content-length" nil t)
202 | (replace-match (format "%d" content-length) nil t))))))))
203 |
204 | (defun http-twiddle-expand-template ()
205 | "Replace any occurences of $|...code...| with the evaluation of ...code..."
206 | (interactive)
207 | (save-excursion
208 | (goto-char (point-min))
209 | (while (re-search-forward "\\$|[^|]+|" nil t)
210 | (let ((d (substring-no-properties (match-string 0) 2)))
211 | (replace-match (format "%s" (eval (car (read-from-string d)))))))))
212 |
213 | (defun http-twiddle-process-filter (process string)
214 | "Process data from the socket by inserting it at the end of the buffer."
215 | (with-current-buffer (process-buffer process)
216 | (let ((inhibit-read-only t))
217 | (goto-char (point-max))
218 | (insert string))))
219 |
220 | (defun http-twiddle-process-sentinel (process what)
221 | (with-current-buffer (process-buffer process)
222 | (goto-char (point-max))
223 | (let ((start (point))
224 | (inhibit-read-only t))
225 | (insert "\nConnection closed\n")
226 | (set-buffer-modified-p nil))))
227 |
228 | (defun http-twiddle-mode-demo ()
229 | (interactive)
230 | (pop-to-buffer (get-buffer-create "*http-twiddle demo*"))
231 | (http-twiddle-mode 1)
232 | (erase-buffer)
233 | (insert "POST / HTTP/1.0\nContent-Length: $Content-Length\nConnection: close\n\nThis is the POST body.\n")
234 | (message "Now press `C-c C-c' and enter a webserver address (e.g. google.com port 80)."))
235 |
236 | (provide 'http-twiddle)
237 |
238 | ;;; http-twiddle.el ends here
239 |
--------------------------------------------------------------------------------