1
1
#!/usr/bin/env node
2
- /*! @license MIT ©2013-2016 Ruben Verborgh, Ghent University - imec */
2
+ /*! @license MIT ©2013-2017 Ruben Verborgh and Ruben Taelman , Ghent University - imec */
3
3
/* Standalone Linked Data Fragments Server */
4
4
5
- var _ = require ( 'lodash' ) ,
6
- fs = require ( 'fs' ) ,
7
- path = require ( 'path' ) ,
8
- cluster = require ( 'cluster' ) ,
9
- LinkedDataFragmentsServer = require ( '../lib/LinkedDataFragmentsServer' ) ,
10
- IndexDatasource = require ( '../lib/datasources/IndexDatasource' ) ,
11
- ViewCollection = require ( '../lib/views/ViewCollection.js' ) ;
5
+ var cluster = require ( 'cluster' ) ,
6
+ ComponentsLoader = require ( 'componentsjs' ) . Loader ;
12
7
13
8
// Parse arguments
14
9
var args = process . argv . slice ( 2 ) ;
15
- if ( args . length < 1 || args . length > 3 || / ^ - - ? h ( e l p ) ? $ / . test ( args [ 0 ] ) ) {
16
- console . log ( 'usage: server config.json [port [workers]]' ) ;
10
+ if ( args . length < 1 || args . length > 4 || / ^ - - ? h ( e l p ) ? $ / . test ( args [ 0 ] ) ) {
11
+ console . log ( 'usage: server config.json [port [workers [componentConfigUri] ]]' ) ;
17
12
return process . exit ( 1 ) ;
18
13
}
19
- var configDefaults = JSON . parse ( fs . readFileSync ( path . join ( __dirname , '../config/config-defaults.json' ) ) ) ,
20
- config = _ . defaults ( JSON . parse ( fs . readFileSync ( args [ 0 ] ) ) , configDefaults ) ,
21
- port = parseInt ( args [ 1 ] , 10 ) || config . port ,
22
- workers = parseInt ( args [ 2 ] , 10 ) || config . workers ,
23
- protocol = config . protocol ,
24
- constructors = { } ;
25
14
26
- // Determine protocol
27
- if ( ! protocol ) {
28
- var protocolMatch = ( config . baseURL || '' ) . match ( / ^ ( \w + ) : / ) ;
29
- protocol = config . protocol = protocolMatch ? protocolMatch [ 1 ] : 'http' ;
30
- }
15
+ var cliPort = parseInt ( args [ 1 ] , 10 ) ,
16
+ cliWorkers = parseInt ( args [ 2 ] , 10 ) ,
17
+ configUri = args [ 3 ] || 'urn:ldf-server:my' ;
18
+
19
+ var loader = new ComponentsLoader ( { scanGlobal : true } ) ;
20
+ loader . registerAvailableModuleResources ( )
21
+ . then ( function ( ) {
22
+ // Start up a cluster master
23
+ if ( cluster . isMaster ) {
24
+ return loader . getConfigConstructorFromUrl ( configUri , args [ 0 ] )
25
+ . then ( function ( constructor ) {
26
+ return constructor . makeArguments ( true ) . then ( function ( args ) {
27
+ startClusterMaster ( args [ 0 ] ) ;
28
+ } ) ;
29
+ } )
30
+ . catch ( function ( e ) {
31
+ console . error ( 'Config error:' ) ;
32
+ console . error ( e ) ;
33
+ process . exit ( 1 ) ;
34
+ } ) ;
35
+ }
36
+ else {
37
+ return loader . instantiateFromUrl ( configUri , args [ 0 ] )
38
+ . then ( function ( worker ) {
39
+ worker . run ( cliPort ) ;
40
+ } )
41
+ . catch ( function ( e ) {
42
+ console . error ( 'Instantiation error:' ) ;
43
+ console . error ( e ) ;
44
+ process . exit ( 1 ) ;
45
+ } ) ;
46
+ }
47
+ } )
48
+ . catch ( function ( e ) {
49
+ console . error ( 'Component definition error:' ) ;
50
+ console . error ( e ) ;
51
+ process . exit ( 1 ) ;
52
+ } ) ;
53
+
54
+ function startClusterMaster ( config ) {
55
+ var workers = cliWorkers || config . workers || 1 ;
31
56
32
- // Start up a cluster master
33
- if ( cluster . isMaster ) {
34
57
// Create workers
35
- console . log ( 'Master %d running on %s://localhost:%d/ .' , process . pid , protocol , port ) ;
58
+ console . log ( 'Master %d running.' , process . pid ) ;
36
59
for ( var i = 0 ; i < workers ; i ++ )
37
60
cluster . fork ( ) ;
38
61
39
62
// Respawn crashed workers
40
63
cluster . on ( 'listening' , function ( worker ) {
41
64
worker . once ( 'exit' , function ( code , signal ) {
42
- if ( ! worker . suicide ) {
65
+ if ( ! worker . exitedAfterDisconnect ) {
43
66
console . log ( 'Worker %d died with %s. Starting new worker.' ,
44
- worker . process . pid , code || signal ) ;
67
+ worker . process . pid , code || signal ) ;
45
68
cluster . fork ( ) ;
46
69
}
47
70
} ) ;
48
71
} ) ;
49
72
73
+ // Disconnect from cluster on SIGINT, so that the process can cleanly terminate
74
+ process . once ( 'SIGINT' , function ( ) {
75
+ cluster . disconnect ( ) ;
76
+ } ) ;
77
+
50
78
// Respawn workers one by one when receiving a SIGHUP signal
51
79
process . on ( 'SIGHUP' , function respawn ( ) {
52
80
console . log ( 'Respawning workers of master %d.' , process . pid ) ;
@@ -66,7 +94,7 @@ if (cluster.isMaster) {
66
94
return newWorker . kill ( ) , respawnNext ( ) ; // Dead workers are replaced automatically
67
95
worker . once ( 'exit' , function ( ) {
68
96
console . log ( 'Worker %d replaces killed worker %d.' ,
69
- newWorker . process . pid , worker . process . pid ) ;
97
+ newWorker . process . pid , worker . process . pid ) ;
70
98
respawnNext ( ) ;
71
99
} ) ;
72
100
worker . kill ( ) ;
@@ -77,7 +105,7 @@ if (cluster.isMaster) {
77
105
function abort ( code , signal ) {
78
106
if ( ! newWorker . suicide ) {
79
107
console . log ( 'Respawning aborted because worker %d died with %s.' ,
80
- newWorker . process . pid , code || signal ) ;
108
+ newWorker . process . pid , code || signal ) ;
81
109
process . addListener ( 'SIGHUP' , respawn ) ;
82
110
process . removeListener ( 'SIGHUP' , respawnPending ) ;
83
111
}
@@ -93,133 +121,3 @@ if (cluster.isMaster) {
93
121
function respawnPending ( ) { console . log ( 'Respawning already in progress' ) ; }
94
122
} ) ;
95
123
}
96
- // Start up a worker
97
- else {
98
- // Configure preset URLs
99
- var baseURL = config . baseURL = config . baseURL . replace ( / \/ ? $ / , '/' ) ,
100
- baseURLRoot = baseURL . match ( / ^ (?: h t t p s ? : \/ \/ [ ^ \/ ] + ) ? / ) [ 0 ] ,
101
- baseURLPath = baseURL . substr ( baseURLRoot . length ) ,
102
- blankNodePath = baseURLRoot ? '/.well-known/genid/' : '' ,
103
- blankNodePrefix = blankNodePath ? baseURLRoot + blankNodePath : 'genid:' ;
104
-
105
- // Create all data sources
106
- var datasources = config . datasources , datasourceBase = baseURLPath . substr ( 1 ) , dereference = config . dereference ;
107
- Object . keys ( datasources ) . forEach ( function ( datasourceName ) {
108
- var datasourceConfig = config . datasources [ datasourceName ] , datasourcePath ;
109
- delete datasources [ datasourceName ] ;
110
- if ( datasourceConfig . enabled !== false ) {
111
- try {
112
- // Avoid illegal URI characters in data source path
113
- datasourcePath = datasourceBase + encodeURI ( datasourceName ) ;
114
- datasources [ datasourcePath ] = datasourceConfig ;
115
- // Set up blank-node-to-IRI translation, with dereferenceable URLs when possible
116
- datasourceConfig . settings = _ . defaults ( datasourceConfig . settings || { } , config ) ;
117
- if ( ! datasourceConfig . settings . blankNodePrefix ) {
118
- datasourceConfig . settings . blankNodePrefix = blankNodePrefix + datasourcePath + '/' ;
119
- if ( blankNodePath )
120
- dereference [ blankNodePath + datasourcePath + '/' ] = datasourcePath ;
121
- }
122
- // Create the data source
123
- var datasource = instantiate ( datasourceConfig , '../lib/datasources/' ) ;
124
- datasource . on ( 'error' , datasourceError ) ;
125
- datasourceConfig . datasource = datasource ;
126
- datasourceConfig . url = baseURLRoot + '/' + datasourcePath + '#dataset' ;
127
- datasourceConfig . title = datasourceConfig . title || datasourceName ;
128
- }
129
- catch ( error ) { datasourceError ( error ) ; }
130
- function datasourceError ( error ) {
131
- delete datasources [ datasourcePath ] ;
132
- process . stderr . write ( 'WARNING: skipped datasource ' + datasourceName + '. ' + error . message + '\n' ) ;
133
- }
134
- }
135
- } ) ;
136
-
137
- // Create index data source
138
- var indexPath = datasourceBase . replace ( / \/ $ / , '' ) ;
139
- datasources [ indexPath ] = datasources [ indexPath ] || {
140
- url : baseURLRoot + '/' + indexPath + '#dataset' ,
141
- hide : true ,
142
- role : 'index' ,
143
- title : 'dataset index' ,
144
- datasource : new IndexDatasource ( { datasources : datasources } ) ,
145
- } ;
146
-
147
- // Set up assets
148
- config . assetsPath = baseURLPath + 'assets/' ;
149
-
150
- // Set up routers, views, and controllers
151
- config . routers = instantiateAll ( config . routers , '../lib/routers/' ) ;
152
- config . views = new ViewCollection ( ) ;
153
- config . views . addViews ( instantiateAll ( findFiles ( '../lib/views' , / \. j s $ / ) ) ) ;
154
- config . controllers = instantiateAll ( config . controllers , '../lib/controllers/' ) ;
155
-
156
- // Set up logging
157
- var loggingSettings = _ . defaults ( config . logging , configDefaults . logging ) ;
158
- config . log = console . log ;
159
- if ( loggingSettings . enabled ) {
160
- var accesslog = require ( 'access-log' ) ;
161
- config . accesslogger = function ( request , response ) {
162
- accesslog ( request , response , loggingSettings . format , function ( logEntry ) {
163
- if ( loggingSettings . file ) {
164
- fs . appendFile ( loggingSettings . file , logEntry + '\n' , function ( error ) {
165
- error && process . stderr . write ( 'Error when writing to access log file: ' + error ) ;
166
- } ) ;
167
- }
168
- else console . log ( logEntry ) ;
169
- } ) ;
170
- } ;
171
- }
172
-
173
- // Create server, and start it when all data sources are ready
174
- var server = new LinkedDataFragmentsServer ( config ) ,
175
- pending = _ . size ( datasources ) ;
176
- _ . each ( datasources , function ( settings ) {
177
- var ready = _ . once ( startWhenReady ) ;
178
- settings . datasource . once ( 'initialized' , ready ) ;
179
- settings . datasource . once ( 'error' , ready ) ;
180
- } ) ;
181
- function startWhenReady ( ) {
182
- if ( ! -- pending ) {
183
- server . listen ( port ) ;
184
- console . log ( 'Worker %d running on %s://localhost:%d/.' , process . pid , protocol , port ) ;
185
- }
186
- }
187
-
188
- // Terminate gracefully if possible
189
- process . once ( 'SIGINT' , function ( ) {
190
- console . log ( 'Stopping worker' , process . pid ) ;
191
- server . stop ( ) ;
192
- process . on ( 'SIGINT' , function ( ) { process . exit ( 1 ) ; } ) ;
193
- } ) ;
194
- }
195
-
196
-
197
- // Instantiates an object from the given description
198
- function instantiate ( description , includePath ) {
199
- var type = description . type || description ,
200
- typePath = path . join ( includePath ? path . resolve ( __dirname , includePath ) : '' , type ) ,
201
- Constructor = constructors [ typePath ] || ( constructors [ typePath ] = require ( typePath ) ) ,
202
- extensions = config . extensions && config . extensions [ type ] || [ ] ,
203
- settings = _ . defaults ( description . settings || { } , {
204
- extensions : extensions . map ( function ( x ) { return instantiate ( x , includePath ) ; } ) ,
205
- } , config ) ;
206
- return new Constructor ( settings , config ) ;
207
- }
208
-
209
- // Instantiates all objects from the given descriptions
210
- function instantiateAll ( descriptions , includePath ) {
211
- return ( _ . isArray ( descriptions ) ? _ . map : _ . mapValues ) ( descriptions ,
212
- function ( description ) { return instantiate ( description , includePath ) ; } ) ;
213
- }
214
-
215
- // Recursively finds files in a folder whose name matches the pattern
216
- function findFiles ( folder , pattern , includeCurrentFolder ) {
217
- folder = path . resolve ( __dirname , folder ) ;
218
- return _ . flatten ( _ . compact ( fs . readdirSync ( folder ) . map ( function ( name ) {
219
- name = path . join ( folder , name ) ;
220
- if ( fs . statSync ( name ) . isDirectory ( ) )
221
- return findFiles ( name , pattern , true ) ;
222
- else if ( includeCurrentFolder && pattern . test ( name ) )
223
- return name ;
224
- } ) ) ) ;
225
- }
0 commit comments