├── 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 | --------------------------------------------------------------------------------