summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJannis R <mail@jannisr.de>2020-05-02 15:32:36 +0200
committerJannis R <mail@jannisr.de>2020-05-02 16:35:50 +0200
commit1b5af6f4bdfca939e780ae70bd6e1819425824f7 (patch)
tree4e3fb0c4957680348c473ff45e3478780d1ba55c
parent635b65aa8af3b35a4ea2c8a5fea805853942cbf4 (diff)
move code, server example :memo:
-rw-r--r--example.js3
-rw-r--r--examples/server.js37
-rw-r--r--index.js78
-rw-r--r--lib/request-parser.js (renamed from lib/parser.js)9
-rw-r--r--lib/util.js14
-rw-r--r--package.json4
-rw-r--r--server.js80
7 files changed, 140 insertions, 85 deletions
diff --git a/example.js b/example.js
deleted file mode 100644
index eba3ac4..0000000
--- a/example.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict'
-
-// todo
diff --git a/examples/server.js b/examples/server.js
new file mode 100644
index 0000000..bff88e3
--- /dev/null
+++ b/examples/server.js
@@ -0,0 +1,37 @@
+'use strict'
+
+const createCert = require('create-cert')
+const {
+ createServer: createGeminiServer,
+ DEFAULT_PORT,
+} = require('..')
+
+const onRequest = (req, res) => {
+ if (req.path === '/foo') {
+ res.write('foo,')
+ res.end('bar')
+ } else {
+ res.gone()
+ }
+}
+
+const onError = (err) => {
+ console.error(err)
+ process.exit(1)
+}
+
+createCert('example.org')
+.then((keys) => {
+ const server = createGeminiServer({
+ tlsOpt: keys,
+ // todo: SNICallback
+ }, onRequest)
+ server.on('error', onError)
+
+ server.listen(DEFAULT_PORT, (err) => {
+ if (err) return onError(err)
+ const {address, port} = server.address()
+ console.info(`listening on ${address}:${port}`)
+ })
+})
+.catch(onError)
diff --git a/index.js b/index.js
index 470b21d..ee44e81 100644
--- a/index.js
+++ b/index.js
@@ -1,78 +1,6 @@
'use strict'
-const {createServer: createTlsServer} = require('tls')
-const {EventEmitter} = require('events')
-const {createParser} = require('./lib/parser')
-const createResponse = require('./lib/response')
-
-const ALPN_ID = 'gemini' // todo: clarify this
-
-const createGeminiServer = (opt = {}, onRequest) => {
- if (typeof opt === 'function') {
- onRequest = opt
- opt = {}
- }
- const {
- tlsOpt,
- } = {
- tlsOpt: {},
- ...opt,
- }
-
- const onConnection = (socket) => {
- const req = createParser()
- socket.pipe(req)
- socket.once('error', (err) => {
- socket.unpipe(req)
- req.destroy(err)
- })
-
- const close = () => {
- socket.destroy()
- req.destroy()
- }
- let timeout = setTimeout(close, 20 * 1000)
-
- req.once('header', (header) => {
- clearTimeout(timeout)
-
- // prepare req
- req.socket = socket
- req.url = header.url
- const url = new URL(header.url, 'http://foo/')
- req.path = url.pathname
- // todo: req.abort(), req.destroy()
-
- // prepare res
- const res = createResponse()
- res.pipe(socket)
- res.once('error', (err) => {
- console.error('error', err)
- res.unpipe(socket)
- socket.destroy(err)
- })
- Object.defineProperty(res, 'socket', {value: socket})
-
- onRequest(req, res)
- server.emit('request', req, res)
- })
- }
-
- const server = createTlsServer({
- // ALPNProtocols: [ALPN_ID],
- // https://gemini.circumlunar.space/docs/spec-spec.txt#1.4.1
- // > Servers MUST use TLS version 1.2 or higher and SHOULD use TLS version
- // > 1.3 or higher.
- minVersion: 'TLSv1.2',
- ...tlsOpt,
- }, onConnection)
-
- return server
+module.exports = {
+ createServer: require('./server'),
+ ...require('./lib/util'),
}
-
-// https://gemini.circumlunar.space/docs/spec-spec.txt, 1.
-// > When Gemini is served over TCP/IP, servers should listen on port 1965
-// > (the first manned Gemini mission, Gemini 3, flew in March '65).
-createGeminiServer.DEFAULT_PORT = 1965
-
-module.exports = createGeminiServer
diff --git a/lib/parser.js b/lib/request-parser.js
index a66a323..04c5cd6 100644
--- a/lib/parser.js
+++ b/lib/request-parser.js
@@ -12,7 +12,7 @@ const {MESSAGES} = require('./statuses')
const CRLF = '\r\n'
const MAX_HEADER_SIZE = 1024 + CRLF.length
-const createParser = () => {
+const createRequestParser = () => {
let headerParsed = false
let peek = Buffer.alloc(0)
@@ -63,7 +63,7 @@ const createParser = () => {
return out
}
-// const p = createParser()
+// const p = createRequestParser()
// p.on('error', console.error)
// p.on('header', h => console.log('header', h))
// p.on('data', d => console.log('data', d.toString('utf8')))
@@ -72,7 +72,4 @@ const createParser = () => {
// p.write(b('le.org/foo?bar#baz\r\nhel'))
// p.end(b('lo server!'))
-module.exports = {
- MESSAGES,
- createParser,
-}
+module.exports = createRequestParser
diff --git a/lib/util.js b/lib/util.js
new file mode 100644
index 0000000..3d46bfb
--- /dev/null
+++ b/lib/util.js
@@ -0,0 +1,14 @@
+'use strict'
+
+// todo: clarify if ALPN is wanted & if this ID is correct
+const ALPN_ID = 'gemini'
+
+// https://gemini.circumlunar.space/docs/spec-spec.txt, 1.
+// > When Gemini is served over TCP/IP, servers should listen on port 1965
+// > (the first manned Gemini mission, Gemini 3, flew in March '65).
+const DEFAULT_PORT = 1965
+
+module.exports = {
+ ALPN_ID,
+ DEFAULT_PORT,
+}
diff --git a/package.json b/package.json
index a7c8fa3..e2fe031 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,9 @@
"main": "index.js",
"files": [
"index.js",
+ "server.js",
"lib",
- "example.js"
+ "examples"
],
"keywords": [
"gemini",
@@ -24,6 +25,7 @@
"dependencies": {
},
"devDependencies": {
+ "create-cert": "^1.0.6"
},
"scripts": {
}
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..344728c
--- /dev/null
+++ b/server.js
@@ -0,0 +1,80 @@
+'use strict'
+
+const {createServer: createTlsServer} = require('tls')
+const {EventEmitter} = require('events')
+const createParser = require('./lib/request-parser')
+const createResponse = require('./lib/response')
+const {ALPN_ID} = require('./lib/util')
+
+const createGeminiServer = (opt = {}, onRequest) => {
+ if (typeof opt === 'function') {
+ onRequest = opt
+ opt = {}
+ }
+ const {
+ tlsOpt,
+ } = {
+ tlsOpt: {},
+ ...opt,
+ }
+
+ const onConnection = (socket) => {
+ const req = createParser()
+ socket.pipe(req)
+ socket.once('error', (err) => {
+ socket.unpipe(req)
+ req.destroy(err)
+ })
+
+ const close = () => {
+ socket.destroy()
+ req.destroy()
+ }
+ let timeout = setTimeout(close, 20 * 1000)
+
+ req.once('header', (header) => {
+ clearTimeout(timeout)
+
+ // prepare req
+ req.socket = socket
+ req.url = header.url
+ const url = new URL(header.url, 'http://foo/')
+ req.path = url.pathname
+ // todo: req.abort(), req.destroy()
+
+ // prepare res
+ const res = createResponse()
+ res.pipe(socket)
+ res.once('error', (err) => {
+ console.error('error', err)
+ res.unpipe(socket)
+ socket.destroy(err)
+ })
+ Object.defineProperty(res, 'socket', {value: socket})
+
+ onRequest(req, res)
+ server.emit('request', req, res)
+ })
+ }
+
+ const server = createTlsServer({
+ ALPNProtocols: [ALPN_ID],
+ // https://gemini.circumlunar.space/docs/spec-spec.txt, 1.4.1
+ // > Servers MUST use TLS version 1.2 or higher and SHOULD use TLS version
+ // > 1.3 or higher.
+ minVersion: 'TLSv1.2',
+ requestCert: !!alwaysRequireClientCert,
+ // > Gemini requests typically will be made without a client
+ // > certificate being sent to the server. If a requested resource
+ // > is part of a server-side application which requires persistent
+ // > state, a Gemini server can [...] request that the client repeat
+ // the request with a "transient certificate" to initiate a client
+ // > certificate section.
+ rejectUnauthorized: false,
+ ...tlsOpt,
+ }, onConnection)
+
+ return server
+}
+
+module.exports = createGeminiServer