Codebase list golang-github-rsc-devweb / 29cc9e1
devweb: initial commit Copied from code.google.com/p/rsc/devweb with modifications. Russ Cox 8 years ago
4 changed file(s) with 377 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Copyright (c) 2009 The Go Authors. All rights reserved.
1
2 Redistribution and use in source and binary forms, with or without
3 modification, are permitted provided that the following conditions are
4 met:
5
6 * Redistributions of source code must retain the above copyright
7 notice, this list of conditions and the following disclaimer.
8 * Redistributions in binary form must reproduce the above
9 copyright notice, this list of conditions and the following disclaimer
10 in the documentation and/or other materials provided with the
11 distribution.
12 * Neither the name of Google Inc. nor the names of its
13 contributors may be used to endorse or promote products derived from
14 this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 This repo holds a program that lets you work on a Go web server
1 and have it automatically recompile on each request, like in the
2 App Engine local development SDK.
3
4 go get rsc.io/devweb
5
6 See https://godoc.org/rsc.io/devweb.
0 // Copyright 2011 The Go Authors. All rights reserved.
1 // Use of this source code is governed by a BSD-style
2 // license that can be found in the LICENSE file.
3
4 // Devweb is a simple environment for developing a web server.
5 // It runs its own web server on the given address and proxies
6 // all requests to the http server program named by importpath.
7 // It takes care of recompiling and restarting the program as needed.
8 //
9 // The server program should be a trivial main program like:
10 //
11 // package main
12 //
13 // import (
14 // "rsc.io/devweb/slave"
15 //
16 // _ "this/package"
17 // _ "that/package"
18 // )
19 //
20 // func main() {
21 // slave.Main()
22 // }
23 //
24 // The import _ lines import packages that register HTTP handlers,
25 // like in an App Engine program.
26 //
27 // As you make changes to this/package or that/package (or their
28 // dependencies), devweb recompiles and relaunches the servers as
29 // needed to serve requests.
30 //
31 package main
32
33 // BUG(rsc): Devweb should probably
34
35 import (
36 "bufio"
37 "bytes"
38 "encoding/json"
39 "flag"
40 "fmt"
41 "io"
42 "log"
43 "net"
44 "net/http"
45 "os"
46 "os/exec"
47 "path/filepath"
48 "sync"
49 "time"
50 )
51
52 func usage() {
53 fmt.Fprint(os.Stderr, `usage: devweb [-addr :8000] importpath
54
55 Devweb runs a web server on the given address and proxies all requests
56 to the http server program named by importpath. It takes care of
57 recompiling and restarting the program as needed.
58
59 The http server program must itself have a -addr argument that
60 says which TCP port to listen on.
61 `,
62 )
63 }
64
65 var addr = flag.String("addr", "localhost:8000", "web service address")
66 var rootPackage string
67
68 func main() {
69 flag.Usage = usage
70 flag.Parse()
71 args := flag.Args()
72 if len(args) != 1 {
73 usage()
74 }
75 rootPackage = args[0]
76
77 log.Fatal(http.ListenAndServe(*addr, http.HandlerFunc(relay)))
78 }
79
80 func relay(w http.ResponseWriter, req *http.Request) {
81 defer func() {
82 if err := recover(); err != nil {
83 http.Error(w, fmt.Sprint(err), 200)
84 }
85 }()
86
87 c, proxy, err := buildProxy()
88 if err != nil {
89 panic(err)
90 }
91 defer c.Close()
92 _ = proxy
93
94 outreq := new(http.Request)
95 *outreq = *req // includes shallow copies of maps, but okay
96
97 outreq.Proto = "HTTP/1.1"
98 outreq.ProtoMajor = 1
99 outreq.ProtoMinor = 1
100 outreq.Close = false
101
102 // Remove the connection header to the backend. We want a
103 // persistent connection, regardless of what the client sent
104 // to us. This is modifying the same underlying map from req
105 // (shallow copied above) so we only copy it if necessary.
106 if outreq.Header.Get("Connection") != "" {
107 outreq.Header = make(http.Header)
108 copyHeader(outreq.Header, req.Header)
109 outreq.Header.Del("Connection")
110 }
111
112 outreq.Write(c)
113
114 br := bufio.NewReader(c)
115 resp, err := http.ReadResponse(br, outreq)
116 if err != nil {
117 panic(err)
118 }
119
120 copyHeader(w.Header(), resp.Header)
121 w.WriteHeader(resp.StatusCode)
122
123 if resp.Body != nil {
124 io.Copy(w, resp.Body)
125 }
126 }
127
128 func copyHeader(dst, src http.Header) {
129 for k, vv := range src {
130 for _, v := range vv {
131 dst.Add(k, v)
132 }
133 }
134 }
135
136 type cmdProxy struct {
137 cmd *exec.Cmd
138 addr string
139 }
140
141 func (p *cmdProxy) kill() {
142 if p == nil {
143 return
144 }
145 p.cmd.Process.Kill()
146 p.cmd.Wait()
147 }
148
149 var proxyInfo struct {
150 sync.Mutex
151 build time.Time
152 check time.Time
153 active *cmdProxy
154 err error
155 }
156
157 func buildProxy() (c net.Conn, proxy *cmdProxy, err error) {
158 p := &proxyInfo
159
160 t := time.Now()
161 p.Lock()
162 defer p.Unlock()
163 if t.Before(p.check) {
164 // We waited for the lock while someone else dialed.
165 // If we can connect, done.
166 if p.active != nil {
167 if c, err := net.DialTimeout("tcp", p.active.addr, 5*time.Second); err == nil {
168 return c, p.active, nil
169 }
170 }
171 }
172
173 defer func() {
174 p.err = err
175 p.check = time.Now()
176 }()
177
178 pkgs, err := loadPackage(rootPackage)
179 if err != nil {
180 return nil, nil, fmt.Errorf("load %s: %s", rootPackage, err)
181 }
182
183 deps := pkgs[0].Deps
184 if len(deps) > 0 && deps[0] == "C" {
185 deps = deps[1:]
186 }
187 pkgs1, err := loadPackage(deps...)
188 if err != nil {
189 return nil, nil, fmt.Errorf("load %v: %s", deps, err)
190 }
191 pkgs = append(pkgs, pkgs1...)
192
193 var latest time.Time
194
195 for _, pkg := range pkgs {
196 var files []string
197 files = append(files, pkg.GoFiles...)
198 files = append(files, pkg.CFiles...)
199 files = append(files, pkg.HFiles...)
200 files = append(files, pkg.SFiles...)
201 files = append(files, pkg.CgoFiles...)
202
203 for _, file := range files {
204 if fi, err := os.Stat(filepath.Join(pkg.Dir, file)); err == nil && fi.ModTime().After(latest) {
205 latest = fi.ModTime()
206 }
207 }
208 }
209
210 if latest.After(p.build) {
211 p.active.kill()
212 p.active = nil
213
214 out, err := exec.Command("go", "build", "-i", "-o", "prox.exe", rootPackage).CombinedOutput()
215 if len(out) > 0 {
216 return nil, nil, fmt.Errorf("%s", out)
217 }
218 if err != nil {
219 return nil, nil, err
220 }
221
222 p.build = latest
223 }
224
225 // If we can connect, done.
226 if p.active != nil {
227 if c, err := net.DialTimeout("tcp", p.active.addr, 5*time.Second); err == nil {
228 return c, p.active, nil
229 }
230 }
231
232 // Otherwise, start a new server.
233 p.active.kill()
234 p.active = nil
235
236 l, err := net.Listen("tcp", "localhost:0")
237 if err != nil {
238 return nil, nil, err
239 }
240 addr := l.Addr().String()
241
242 cmd := exec.Command("./prox.exe", "LISTEN_STDIN")
243 cmd.Stdin, err = l.(*net.TCPListener).File()
244 cmd.Stdout = os.Stdout
245 cmd.Stderr = os.Stderr
246 if err != nil {
247 l.Close()
248 return nil, nil, err
249 }
250 err = cmd.Start()
251 l.Close()
252
253 if err != nil {
254 return nil, nil, err
255 }
256
257 c, err = net.DialTimeout("tcp", addr, 5*time.Second)
258 if err != nil {
259 return nil, nil, err
260 }
261
262 p.active = &cmdProxy{cmd, addr}
263 return c, p.active, nil
264 }
265
266 type Pkg struct {
267 ImportPath string
268 Dir string
269 GoFiles []string
270 CFiles []string
271 HFiles []string
272 SFiles []string
273 CgoFiles []string
274 Deps []string
275 }
276
277 func loadPackage(name ...string) ([]*Pkg, error) {
278 args := []string{"list", "-json"}
279 args = append(args, name...)
280 var stderr bytes.Buffer
281 cmd := exec.Command("go", args...)
282 r, err := cmd.StdoutPipe()
283 if err != nil {
284 return nil, err
285 }
286 cmd.Stderr = &stderr
287 if err := cmd.Start(); err != nil {
288 return nil, err
289 }
290
291 dec := json.NewDecoder(r)
292 var pkgs []*Pkg
293 for {
294 p := new(Pkg)
295 if err := dec.Decode(p); err != nil {
296 if err == io.EOF {
297 break
298 }
299 cmd.Process.Kill()
300 return nil, err
301 }
302 pkgs = append(pkgs, p)
303 }
304
305 err = cmd.Wait()
306 if b := stderr.Bytes(); len(b) > 0 {
307 return nil, fmt.Errorf("%s", b)
308 }
309 if err != nil {
310 return nil, err
311 }
312 if len(pkgs) != len(name) {
313 return nil, fmt.Errorf("found fewer packages than expected")
314 }
315 return pkgs, nil
316 }
0 // Copyright 2011 The Go Authors. All rights reserved.
1 // Use of this source code is governed by a BSD-style
2 // license that can be found in the LICENSE file.
3
4 package slave
5
6 import (
7 "fmt"
8 "log"
9 "net"
10 "net/http"
11 "os"
12 )
13
14 func Main() {
15 if len(os.Args) != 2 || os.Args[1] != "LISTEN_STDIN" {
16 fmt.Fprintf(os.Stderr, "devweb slave must be invoked by devweb\n")
17 os.Exit(2)
18 }
19 l, err := net.FileListener(os.Stdin)
20 if err != nil {
21 log.Fatal(err)
22 }
23 os.Stdin.Close()
24 log.Fatal(http.Serve(l, nil))
25 }