|
@@ -1,5 +1,6 @@
|
|
|
+Smalltalk current createPackage: 'FileServer'!
|
|
|
Object subclass: #FileServer
|
|
|
- instanceVariableNames: 'path http fs url port basePath util username password'
|
|
|
+ instanceVariableNames: 'path http fs url port basePath util username password fallbackPage'
|
|
|
package: 'FileServer'!
|
|
|
|
|
|
!FileServer methodsFor: 'accessing'!
|
|
@@ -12,6 +13,18 @@ basePath: aString
|
|
|
basePath := aString
|
|
|
!
|
|
|
|
|
|
+fallbackPage
|
|
|
+ ^fallbackPage
|
|
|
+!
|
|
|
+
|
|
|
+fallbackPage: aString
|
|
|
+ fallbackPage := aString
|
|
|
+!
|
|
|
+
|
|
|
+password: aPassword
|
|
|
+ password := aPassword.
|
|
|
+!
|
|
|
+
|
|
|
port
|
|
|
^port
|
|
|
!
|
|
@@ -24,10 +37,6 @@ username: aUsername
|
|
|
username := aUsername.
|
|
|
!
|
|
|
|
|
|
-password: aPassword
|
|
|
- password := aPassword.
|
|
|
-!
|
|
|
-
|
|
|
username: aUsername password: aPassword
|
|
|
username := aUsername.
|
|
|
password := aPassword.
|
|
@@ -35,6 +44,15 @@ username: aUsername password: aPassword
|
|
|
|
|
|
!FileServer methodsFor: 'initialization'!
|
|
|
|
|
|
+checkDirectoryLayout
|
|
|
+ (fs existsSync: self basePath, 'index.html') ifFalse: [
|
|
|
+ console warn: 'Warning: project directory does not contain index.html'].
|
|
|
+ (fs existsSync: self basePath, 'st') ifFalse: [
|
|
|
+ console warn: 'Warning: project directory is missing an "st" directory'].
|
|
|
+ (fs existsSync: self basePath, 'js') ifFalse: [
|
|
|
+ console warn: 'Warning: project directory is missing a "js" directory'].
|
|
|
+!
|
|
|
+
|
|
|
initialize
|
|
|
super initialize.
|
|
|
path := self require: 'path'.
|
|
@@ -45,28 +63,11 @@ initialize
|
|
|
port := self class defaultPort.
|
|
|
username := nil.
|
|
|
password := nil.
|
|
|
-!
|
|
|
-
|
|
|
-checkDirectoryLayout
|
|
|
- (fs existsSync: self basePath, 'index.html') ifFalse: [
|
|
|
- console warn: 'Warning: project directory does not contain index.html'].
|
|
|
- (fs existsSync: self basePath, 'st') ifFalse: [
|
|
|
- console warn: 'Warning: project directory is missing an "st" directory'].
|
|
|
- (fs existsSync: self basePath, 'js') ifFalse: [
|
|
|
- console warn: 'Warning: project directory is missing a "js" directory'].
|
|
|
+ fallbackPage := nil.
|
|
|
! !
|
|
|
|
|
|
!FileServer methodsFor: 'private'!
|
|
|
|
|
|
-require: aModuleString
|
|
|
- "call to the require function"
|
|
|
- ^require value: aModuleString
|
|
|
-!
|
|
|
-
|
|
|
-writeData: data toFileNamed: aFilename
|
|
|
- console log: aFilename
|
|
|
-!
|
|
|
-
|
|
|
base64Decode: aString
|
|
|
<return (new Buffer(aString, 'base64').toString())>
|
|
|
!
|
|
@@ -94,19 +95,19 @@ isAuthenticated: aRequest
|
|
|
ifTrue: [^true]
|
|
|
ifFalse: [^false]
|
|
|
].
|
|
|
+!
|
|
|
+
|
|
|
+require: aModuleString
|
|
|
+ "call to the require function"
|
|
|
+ ^require value: aModuleString
|
|
|
+!
|
|
|
+
|
|
|
+writeData: data toFileNamed: aFilename
|
|
|
+ console log: aFilename
|
|
|
! !
|
|
|
|
|
|
!FileServer methodsFor: 'request handling'!
|
|
|
|
|
|
-handleRequest: aRequest respondTo: aResponse
|
|
|
- aRequest method = 'PUT'
|
|
|
- ifTrue: [self handlePUTRequest: aRequest respondTo: aResponse].
|
|
|
- aRequest method = 'GET'
|
|
|
- ifTrue:[self handleGETRequest: aRequest respondTo: aResponse].
|
|
|
- aRequest method = 'OPTIONS'
|
|
|
- ifTrue:[self handleOPTIONSRequest: aRequest respondTo: aResponse]
|
|
|
-!
|
|
|
-
|
|
|
handleGETRequest: aRequest respondTo: aResponse
|
|
|
| uri filename |
|
|
|
uri := (url parse: aRequest url) pathname.
|
|
@@ -117,6 +118,15 @@ handleGETRequest: aRequest respondTo: aResponse
|
|
|
ifTrue: [self respondFileNamed: filename to: aResponse]]
|
|
|
!
|
|
|
|
|
|
+handleOPTIONSRequest: aRequest respondTo: aResponse
|
|
|
+ aResponse writeHead: 200 options: #{'Access-Control-Allow-Origin' -> '*'.
|
|
|
+ 'Access-Control-Allow-Methods' -> 'GET, PUT, POST, DELETE, OPTIONS'.
|
|
|
+ 'Access-Control-Allow-Headers' -> 'Content-Type, Accept'.
|
|
|
+ 'Content-Length' -> 0.
|
|
|
+ 'Access-Control-Max-Age' -> 10}.
|
|
|
+ aResponse end
|
|
|
+!
|
|
|
+
|
|
|
handlePUTRequest: aRequest respondTo: aResponse
|
|
|
| file stream |
|
|
|
(self isAuthenticated: aRequest)
|
|
@@ -142,13 +152,26 @@ handlePUTRequest: aRequest respondTo: aResponse
|
|
|
stream writable ifTrue: [stream end]]
|
|
|
!
|
|
|
|
|
|
-handleOPTIONSRequest: aRequest respondTo: aResponse
|
|
|
- aResponse writeHead: 200 options: #{'Access-Control-Allow-Origin' -> '*'.
|
|
|
- 'Access-Control-Allow-Methods' -> 'GET, PUT, POST, DELETE, OPTIONS'.
|
|
|
- 'Access-Control-Allow-Headers' -> 'Content-Type, Accept'.
|
|
|
- 'Content-Length' -> 0.
|
|
|
- 'Access-Control-Max-Age' -> 10}.
|
|
|
- aResponse end
|
|
|
+handleRequest: aRequest respondTo: aResponse
|
|
|
+ aRequest method = 'PUT'
|
|
|
+ ifTrue: [self handlePUTRequest: aRequest respondTo: aResponse].
|
|
|
+ aRequest method = 'GET'
|
|
|
+ ifTrue:[self handleGETRequest: aRequest respondTo: aResponse].
|
|
|
+ aRequest method = 'OPTIONS'
|
|
|
+ ifTrue:[self handleOPTIONSRequest: aRequest respondTo: aResponse]
|
|
|
+!
|
|
|
+
|
|
|
+respondAuthenticationRequiredTo: aResponse
|
|
|
+ aResponse
|
|
|
+ writeHead: 401 options: #{'WWW-Authenticate' -> 'Basic realm="Secured Developer Area"'};
|
|
|
+ write: '<html><body>Authentication needed</body></html>';
|
|
|
+ end.
|
|
|
+!
|
|
|
+
|
|
|
+respondCreatedTo: aResponse
|
|
|
+ aResponse
|
|
|
+ writeHead: 201 options: #{'Content-Type' -> 'text/plain'. 'Access-Control-Allow-Origin' -> '*'};
|
|
|
+ end.
|
|
|
!
|
|
|
|
|
|
respondFileNamed: aFilename to: aResponse
|
|
@@ -180,33 +203,21 @@ respondInternalErrorTo: aResponse
|
|
|
end
|
|
|
!
|
|
|
|
|
|
-respondAuthenticationRequiredTo: aResponse
|
|
|
+respondNotCreatedTo: aResponse
|
|
|
aResponse
|
|
|
- writeHead: 401 options: #{'WWW-Authenticate' -> 'Basic realm="Secured Developer Area"'};
|
|
|
- write: '<html><body>Authentication needed</body></html>';
|
|
|
+ writeHead: 400 options: #{'Content-Type' -> 'text/plain'};
|
|
|
+ write: 'File could not be created. Did you forget to create the st/js directories on the server?';
|
|
|
end.
|
|
|
!
|
|
|
|
|
|
respondNotFoundTo: aResponse
|
|
|
+ self fallbackPage isNil ifFalse: [^self respondFileNamed: self fallbackPage to: aResponse].
|
|
|
aResponse
|
|
|
writeHead: 404 options: #{'Content-Type' -> 'text/plain'};
|
|
|
write: '404 Not found';
|
|
|
end
|
|
|
!
|
|
|
|
|
|
-respondNotCreatedTo: aResponse
|
|
|
- aResponse
|
|
|
- writeHead: 400 options: #{'Content-Type' -> 'text/plain'};
|
|
|
- write: 'File could not be created. Did you forget to create the st/js directories on the server?';
|
|
|
- end.
|
|
|
-!
|
|
|
-
|
|
|
-respondCreatedTo: aResponse
|
|
|
- aResponse
|
|
|
- writeHead: 201 options: #{'Content-Type' -> 'text/plain'. 'Access-Control-Allow-Origin' -> '*'};
|
|
|
- end.
|
|
|
-!
|
|
|
-
|
|
|
respondOKTo: aResponse
|
|
|
aResponse
|
|
|
writeHead: 200 options: #{'Content-Type' -> 'text/plain'. 'Access-Control-Allow-Origin' -> '*'};
|
|
@@ -215,25 +226,30 @@ respondOKTo: aResponse
|
|
|
|
|
|
!FileServer methodsFor: 'starting'!
|
|
|
|
|
|
-startOn: aPort
|
|
|
- self port: aPort.
|
|
|
- self start
|
|
|
-!
|
|
|
-
|
|
|
start
|
|
|
(http createServer: [:request :response |
|
|
|
self handleRequest: request respondTo: response])
|
|
|
on: 'error' do: [:error | console log: 'Error starting server: ', error];
|
|
|
on: 'listening' do: [console log: 'Starting file server on port ', self port asString];
|
|
|
listen: self port.
|
|
|
+!
|
|
|
+
|
|
|
+startOn: aPort
|
|
|
+ self port: aPort.
|
|
|
+ self start
|
|
|
! !
|
|
|
|
|
|
FileServer class instanceVariableNames: 'mimeTypes'!
|
|
|
|
|
|
!FileServer class methodsFor: 'accessing'!
|
|
|
|
|
|
-defaultPort
|
|
|
- ^4000
|
|
|
+commandLineActions
|
|
|
+ ^#{
|
|
|
+ '-p' -> [:fileServer :value | fileServer port: value].
|
|
|
+ '--username' -> [:fileServer :value | fileServer username: value].
|
|
|
+ '--password' -> [:fileServer :value | fileServer password: value].
|
|
|
+ '--fallback-page' -> [:fileServer :value | fileServer fallbackPage: value]
|
|
|
+ }
|
|
|
!
|
|
|
|
|
|
defaultMimeTypes
|
|
@@ -650,20 +666,16 @@ defaultMimeTypes
|
|
|
}
|
|
|
!
|
|
|
|
|
|
-mimeTypes
|
|
|
- ^mimeTypes ifNil: [mimeTypes := self defaultMimeTypes]
|
|
|
+defaultPort
|
|
|
+ ^4000
|
|
|
!
|
|
|
|
|
|
mimeTypeFor: aString
|
|
|
^self mimeTypes at: (aString replace: '.*[\.]' with: '') ifAbsent: ['text/plain']
|
|
|
!
|
|
|
|
|
|
-commandLineActions
|
|
|
- ^#{
|
|
|
- '-p' -> [:fileServer :value | fileServer port: value].
|
|
|
- '--username' -> [:fileServer :value | fileServer username: value].
|
|
|
- '--password' -> [:fileServer :value | fileServer password: value]
|
|
|
- }
|
|
|
+mimeTypes
|
|
|
+ ^mimeTypes ifNil: [mimeTypes := self defaultMimeTypes]
|
|
|
! !
|
|
|
|
|
|
!FileServer class methodsFor: 'initialization'!
|