Commit b1513993 authored by roeland lanparty's avatar roeland lanparty
Browse files

initial import

parent c9a4c6c7
Pipeline #11892 failed with stages
FROM node:8
WORKDIR /app
COPY . .
RUN yarn install --non-interactive --frozen-lockfile
RUN make ci-test
RUN make lib
# prune modules
RUN yarn install --non-interactive --frozen-lockfile --production
EXPOSE 8080
ENV PORT 8080
ENV NODE_ENV production
CMD [ "node", "lib/server.js" ]
SHELL := /bin/bash
PATH := ./node_modules/.bin:$(PATH)
SRC_FILES := $(shell find src -name '*.ts')
all: lib
lib: $(SRC_FILES) node_modules tsconfig.json
tsc -p tsconfig.json --outDir lib
VERSION="$$(node -p 'require("./package.json").version')"; \
BUILD="$$(git rev-parse --short HEAD)-$$(date +%s)"; \
echo "module.exports = '$${VERSION}-$${BUILD}';" > lib/version.js
touch lib
.PHONY: devserver
devserver: node_modules
@onchange -i 'src/**/*.ts' 'config/*' -- ts-node src/server.ts | bunyan -o short
.PHONY: coverage
coverage: node_modules
NODE_ENV=test nyc -r html -r text -e .ts -i ts-node/register mocha --reporter nyan --require ts-node/register test/*.ts
.PHONY: test
test: node_modules
NODE_ENV=test mocha --require ts-node/register test/*.ts --grep '$(grep)'
.PHONY: ci-test
ci-test: node_modules
tslint -p tsconfig.json -c tslint.json
NODE_ENV=test nyc -r lcov -e .ts -i ts-node/register mocha --reporter tap --require ts-node/register test/*.ts
.PHONY: lint
lint: node_modules
tslint -p tsconfig.json -c tslint.json -t stylish --fix
node_modules: package.json
yarn install --non-interactive --frozen-lockfile
.PHONY: clean
clean:
rm -rf lib/
.PHONY: distclean
distclean: clean
rm -rf node_modules/
lineman
=======
JSON RPC 2.0 WebSocket <> HTTP bridge
machine:
services:
- docker
dependencies:
override:
- echo "Ignore CircleCI detected dependencies"
test:
override:
- docker build -t steemit/lineman .
port = 'PORT'
num_workers = 'NUM_WORKERS'
rpc_node = 'RPC_NODE'
[log]
__name = 'LOG_STREAMS'
__format = 'json'
# lineman default configuration, override in your config/local-development.toml
# port to listen on
port = 8090
# application name used for logging and service namespace
name = 'lineman'
# number of worker processes to spawn, 0 = autodetect
num_workers = 1
# http(s) steem rpc node where rpc calls are proxied to
rpc_node = 'https://api.steemitdev.com'
# logging output, can be repeated
[[log]]
level = 'debug'
out = 'stdout' # path or stdout/err
num_workers = 0
port = 8080
rpc_node = 'https://api.steemit.com'
[[log]]
level = 'info'
out = 'stdout'
rpc_node = 'https://api.steemit.com'
[[log]]
level = 'fatal'
out = 'stdout'
{
"name": "@steemit/lineman",
"version": "1.0.1",
"private": true,
"scripts": {
"test": "make ci-test"
},
"engines": {
"node": ">=8"
},
"dependencies": {
"bunyan": "^2.0.2",
"config": "^1.26.2",
"toml": "^2.3.3",
"uws": "^9.14.0"
},
"devDependencies": {
"@types/bunyan": "^1.8.2",
"@types/config": "0.0.32",
"@types/mocha": "^2.2.41",
"@types/needle": "^2.0.2",
"@types/node": "^8.0.26",
"@types/uws": "^0.13.1",
"@types/ws": "^3.2.1",
"mocha": "^3.5.0",
"needle": "^2.1.0",
"nyc": "^11.1.0",
"onchange": "^3.2.1",
"ts-node": "^3.3.0",
"tslint": "^5.7.0",
"typescript": "^2.5.0",
"ws": "^4.0.0"
}
}
import * as bunyan from 'bunyan'
import * as config from 'config'
export const logger = bunyan.createLogger({
name: config.get('name'),
streams: (config.get('log') as any[]).map(({level, out}) => {
if (out === 'stdout') {
return {level, stream: process.stdout}
} else if (out === 'stderr') {
return {level, stream: process.stderr}
} else {
return {level, path: out}
}
})
})
/**
* @file Lineman server.
* @author Johan Nordberg <johan@steemit.com>
*/
import * as cluster from 'cluster'
import * as config from 'config'
import * as http from 'http'
import * as https from 'https'
import * as os from 'os'
import {parse as parseUrl} from 'url'
import * as uws from 'uws'
import {logger} from './logger'
export const version = require('./version')
const rpcOpts = parseUrl(config.get('rpc_node')) as https.RequestOptions
rpcOpts.method = 'POST'
const secure = rpcOpts.protocol === 'https:'
const httpx: any = secure ? https : http
const agentOpts = {keepAlive: true}
rpcOpts.agent = secure ? new https.Agent(agentOpts) : new http.Agent(agentOpts)
export const server = new http.Server((request, response) => {
if (request.method !== 'GET') {
response.writeHead(400)
response.end()
return
}
response.writeHead(200, {'Content-Type': 'application/json'})
response.end(JSON.stringify({ok: true, version, date: new Date()}))
})
export const wss = new uws.Server({server})
wss.on('connection', (socket) => {
const {remoteAddress} = (socket as any)._socket
const log = logger.child({ip: remoteAddress})
log.info('new connection')
socket.onclose = () => { log.info('connection closed') }
socket.onerror = (error) => { log.warn(error, 'socket error') }
socket.onmessage = (message) => {
let reqData: any
try {
reqData = JSON.parse(message.data)
} catch (error) {
socket.send(`{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid request"}`)
return
}
if (!reqData.jsonrpc) {
reqData.jsonrpc = '2.0'
}
log.debug({data: reqData}, 'send')
const request = httpx.request(rpcOpts, (response) => {
const chunks: Buffer[] = []
response.on('data', (chunk) => chunks.push(chunk as Buffer))
response.on('end', () => {
const resData = Buffer.concat(chunks)
log.debug({data: resData.toString()}, 'recv')
socket.send(resData)
})
})
request.setHeader('Content-Type', 'application/json')
request.write(JSON.stringify(reqData))
request.end()
}
})
function run() {
const port = config.get('port')
server.listen(port, () => {
logger.info('running on port %d proxying %s', port, rpcOpts.hostname)
})
}
if (module === require.main) {
let numWorkers = config.get('num_workers')
if (numWorkers === 0) {
numWorkers = os.cpus().length
}
if (numWorkers > 1) {
if (cluster.isMaster) {
logger.info('spawning %d workers', numWorkers)
for (let i = 0; i < numWorkers; i++) {
cluster.fork()
}
} else {
run()
}
} else {
run()
}
}
// replaced by build-script to ${ version }-${ git shortrev }-${ date }
const pkg = require('../package')
module.exports = `${ pkg.version }-dev`
import 'mocha'
import * as assert from 'assert'
import * as needle from 'needle'
import * as WebSocket from 'ws'
import {server} from './../src/server'
describe('server', function() {
const port = process.env['TEST_HTTP_PORT'] ? parseInt(process.env['TEST_HTTP_PORT'] as string) : 63205
before((done) => { server.listen(port, 'localhost', done) })
after((done) => { server.close(done) })
it('should healthcheck', async function() {
const rv = await needle('get', `localhost:${ port }`)
assert(rv.statusCode === 200)
assert(rv.body.ok === true)
})
it('should proxy websockets', function(done) {
this.slow(2 * 1000)
this.timeout(5 * 1000)
const ws = new WebSocket(`ws://localhost:${ port }`)
ws.onerror = done
ws.onopen = () => {
const params = ['database_api', 'get_accounts', [['almost-digital']]]
const request = {id: 1, jsonrpc: '2.0', method: 'call', params}
ws.send(JSON.stringify(request))
ws.onmessage = (response) => {
const data = JSON.parse(response.data.toString())
assert.equal(data.result[0].id, 180270)
assert.equal(data.result[0].name, 'almost-digital')
done()
}
}
})
})
{
"compilerOptions": {
"lib": ["esnext"],
"module": "commonjs",
"moduleResolution": "node",
"target": "esnext"
},
"include": [
"*.ts"
]
}
\ No newline at end of file
{
"compilerOptions": {
"lib": ["esnext"],
"module": "commonjs",
"moduleResolution": "node",
"noImplicitThis": true,
"outDir": "lib",
"strictNullChecks": true,
"target": "esnext"
},
"include": [
"src/**/*.ts"
]
}
\ No newline at end of file
{
"defaultSeverity": "error",
"extends": ["tslint:recommended"],
"rules": {
"indent": [true, "spaces", 4],
"interface-name": [true, "never-prefix"],
"max-classes-per-file": false,
"no-bitwise": false,
"no-string-literal": false,
"no-var-requires": false,
"object-literal-sort-keys": false,
"quotemark": [true, "single", "avoid-escape"],
"semicolon": [true, "never"],
"trailing-comma": [true, "never"],
"triple-equals": [true, "allow-undefined-check"]
}
}
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment