1
+ /*
2
+ * Little server that listens for requests in the form of
3
+ * `${branch}.host/guide/${doc}`, looks up the doc from git and streams
4
+ * it back over the response.
5
+ *
6
+ * It uses subprocesses to access git. It is tempting to use NodeGit to prevent
7
+ * the fork overhead but for the most part the subprocesses are fast enough and
8
+ * I'm worried that NodeGit's `Blog.getContent` methods are synchronous.
9
+ */
10
+
1
11
const child_process = require ( 'child_process' ) ;
2
12
const http = require ( 'http' ) ;
3
13
const url = require ( 'url' ) ;
@@ -9,8 +19,6 @@ const checkTypeOpts = {
9
19
} ;
10
20
const catOpts = {
11
21
'cwd' : '/docs_build/.repos/target_repo.git' ,
12
- 'encoding' : 'buffer' ,
13
- 'max_buffer' : 1024 * 1024 * 20 ,
14
22
} ;
15
23
16
24
const requestHandler = ( request , response ) => {
@@ -23,6 +31,7 @@ const requestHandler = (request, response) => {
23
31
const path = 'html' + parsedUrl . pathname . substring ( '/guide' . length ) ;
24
32
const branch = gitBranch ( request . headers [ 'host' ] ) ;
25
33
const requestedObject = `${ branch } :${ path } ` ;
34
+ // TODO keepalive?
26
35
// TODO include the commit info for the branch in a header
27
36
child_process . execFile ( 'git' , [ 'cat-file' , '-t' , requestedObject ] , checkTypeOpts , ( err , stdout , stderr ) => {
28
37
if ( err ) {
@@ -36,15 +45,51 @@ const requestHandler = (request, response) => {
36
45
}
37
46
return ;
38
47
}
48
+
49
+ response . setHeader ( 'Transfer-Encoding' , 'chunked' ) ;
39
50
const showGitObject = gitShowObject ( requestedObject , stdout . trim ( ) ) ;
40
- child_process . execFile ( 'git' , [ 'cat-file' , 'blob' , showGitObject ] , catOpts , ( err , stdout , stderr ) => {
41
- if ( err ) {
42
- console . warn ( 'unhandled error' , err ) ;
43
- response . statusCode = 500 ;
44
- response . end ( err . message ) ;
51
+ const child = child_process . spawn (
52
+ 'git' , [ 'cat-file' , 'blob' , showGitObject ] , catOpts
53
+ ) ;
54
+
55
+ // We spool stderr into a string because it is never super big.
56
+ child . stderr . setEncoding ( 'utf8' ) ;
57
+ let childstderr = '' ;
58
+ child . stderr . addListener ( 'data' , chunk => {
59
+ childstderr += chunk ;
60
+ } ) ;
61
+
62
+ /* Let node handle all the piping because it handles backpressure properly.
63
+ * We don't let the pipe emit the `end` event though because we'd like to
64
+ * do that manually when the process exits. The chunk size seems looks like
65
+ * it is 4k which looks to come from the child_process. */
66
+ child . stdout . pipe ( response , { end : false } ) ;
67
+
68
+ let exited = false ;
69
+ child . addListener ( 'close' , ( code , signal ) => {
70
+ /* This can get invoked multiple times but we can only do anything
71
+ * the first time so we just drop the second time on the floor. */
72
+ if ( exited ) return ;
73
+ exited = true ;
74
+
75
+ if ( code === 0 && signal === null ) {
76
+ response . end ( ) ;
45
77
return ;
46
78
}
47
- response . end ( stdout ) ;
79
+ /* Note that we're playing a little fast and loose here with the status.
80
+ * If we've already sent a chunk we can't change the status code. We're
81
+ * out of luck. We just terminate the response anyway. The server log
82
+ * is the best we can do for tracking these. */
83
+ console . warn ( 'unhandled error' , code , signal , childstderr ) ;
84
+ response . statusCode = 500 ;
85
+ response . end ( childstderr ) ;
86
+ } ) ;
87
+ child . addListener ( 'error' , err => {
88
+ child . stdout . destroy ( ) ;
89
+ child . stderr . destroy ( ) ;
90
+ console . warn ( 'unhandled error' , err ) ;
91
+ response . statusCode = 500 ;
92
+ response . end ( err . message ) ;
48
93
} ) ;
49
94
} ) ;
50
95
}
0 commit comments