Skip to content
Snippets Groups Projects
Commit b1513993 authored by roeland lanparty's avatar roeland lanparty
Browse files

initial import

parent c9a4c6c7
No related branches found
No related tags found
No related merge requests found
Pipeline #11892 failed
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" ]
Makefile 0 → 100755
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
yarn.lock 0 → 100755
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment