1
1
mirror of https://github.com/leon-ai/leon.git synced 2024-09-11 18:27:21 +03:00

refactor(server): HTTP server and structure (wip)

This commit is contained in:
louistiti 2022-11-14 00:49:24 +08:00
parent b3b10e326f
commit 69e90c3b5f
No known key found for this signature in database
GPG Key ID: 7ECA3DD523793FE6
22 changed files with 184 additions and 31 deletions

View File

@ -4,7 +4,7 @@
# Language currently used
LEON_LANG=en-US
# Server
# HttpServer
LEON_HOST=http://localhost
LEON_PORT=1337

View File

@ -51,7 +51,7 @@ Choose the setup method you want to go for.
### Single-Click
Gitpod will automatically setup an environment and run an instance for you.
Gitpod will automatically set up an environment and run an instance for you.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/leon-ai/leon)
@ -149,7 +149,7 @@ By sponsoring the project you make the project sustainable and faster to develop
The focus is not only limited to the activity you see on GitHub but also a lot of thinking about the direction of the project. Which is naturally related to the overall design, architecture, vision, learning process and so on...
## Contributing to the Python Bridge or TCP Server
## Contributing to the Python Bridge or TCP HttpServer
Leon makes use of two binaries, the Python bridge and the TCP server. These binaries are compiled from Python sources.

View File

@ -68,8 +68,8 @@ If you want to, Leon can communicate with you by being **offline to protect your
> - Skills
> - The web app
> - The hotword node
> - The TCP server (for inter-process communication between Leon and third-party processes such as spaCy)
> - The Python bridge (the connector between Python core and skills)
> - The TCP server (for inter-process communication between Leon and third-party nodes such as spaCy)
> - The Python bridge (the connector between the core and skills made with Python)
### What is Leon able to do?
@ -81,7 +81,7 @@ Sounds good to you? Then let's get started!
## ☁️ Try with a Single-Click
Gitpod will automatically setup an environment and run an instance for you.
Gitpod will automatically set up an environment and run an instance for you.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/leon-ai/leon)

View File

@ -453,7 +453,7 @@ dotenv.config()
if (!fs.existsSync(flitePath)) {
report.can_offline_tts.v = false
LogHelper.warning(
`Cannot find ${flitePath}. You can setup the offline TTS by running: "npm run setup:offline-tts"\n`
`Cannot find ${flitePath}. You can set up the offline TTS by running: "npm run setup:offline-tts"\n`
)
} else {
LogHelper.success(`Found Flite at ${flitePath}\n`)

View File

@ -9,6 +9,6 @@ import setupHotword from './setup-hotword'
try {
await setupHotword()
} catch (e) {
LogHelper.error(`Failed to setup offline hotword: ${e}`)
LogHelper.error(`Failed to set up offline hotword: ${e}`)
}
})()

View File

@ -9,6 +9,6 @@ import setupStt from './setup-stt'
try {
await setupStt()
} catch (e) {
LogHelper.error(`Failed to setup offline STT: ${e}`)
LogHelper.error(`Failed to set up offline STT: ${e}`)
}
})()

View File

@ -9,6 +9,6 @@ import setupTts from './setup-tts'
try {
await setupTts()
} catch (e) {
LogHelper.error(`Failed to setup offline TTS: ${e}`)
LogHelper.error(`Failed to set up offline TTS: ${e}`)
}
})()

View File

@ -6,7 +6,7 @@ import { LogHelper } from '@/helpers/log-helper'
import { OSHelper } from '@/helpers/os-helper'
/**
* Setup offline speech-to-text
* Set up offline speech-to-text
*/
export default () =>
new Promise(async (resolve, reject) => {

View File

@ -6,7 +6,7 @@ import { LogHelper } from '@/helpers/log-helper'
import { OSHelper } from '@/helpers/os-helper'
/**
* Setup offline text-to-speech
* Set up offline text-to-speech
*/
export default () =>
new Promise(async (resolve, reject) => {

View File

@ -4,7 +4,7 @@ import path from 'node:path'
import { LogHelper } from '@/helpers/log-helper'
/**
* Setup Leon's core configuration
* Set up Leon's core configuration
*/
export default () =>
new Promise((resolve) => {

View File

@ -7,7 +7,7 @@ import { LogHelper } from '@/helpers/log-helper'
import { SkillDomainHelper } from '@/helpers/skill-domain-helper'
/**
* Setup skills configuration
* Set up skills configuration
*/
export default () =>
new Promise(async (resolve, reject) => {

View File

@ -0,0 +1,139 @@
import { join } from 'node:path'
import Fastify from 'fastify'
import fastifyStatic from '@fastify/static'
import { Server as SocketIOServer } from 'socket.io'
import { version } from '@@/package.json'
import { LEON_NODE_ENV, HAS_LOGGER, IS_DEVELOPMENT_ENV } from '@/constants'
import { LogHelper } from '@/helpers/log-helper'
import { DateHelper } from '@/helpers/date-helper'
import corsMidd from '@/core/http-server/plugins/cors'
import otherMidd from '@/core/http-server/plugins/other'
import infoPlugin from '@/core/http-server/api/info'
import downloadsPlugin from '@/core/http-server/api/downloads'
// import keyMidd from '@/core/http-server/plugins/key'
import server from '@/core/http-server/old-server'
const API_VERSION = 'v1'
export default class HTTPServer {
private static instance: HTTPServer
private fastify = Fastify()
private httpServer = this.fastify.server
constructor(private readonly host: string, private readonly port: number) {
if (!HTTPServer.instance) {
LogHelper.title('HTTP Server')
LogHelper.success('New instance')
HTTPServer.instance = this
}
this.host = host
this.port = port
}
/**
* Server entry point
*/
public async init(): Promise<void> {
this.fastify.addHook('onRequest', corsMidd)
this.fastify.addHook('preValidation', otherMidd)
LogHelper.title('Initialization')
LogHelper.info(`The current env is ${LEON_NODE_ENV}`)
LogHelper.info(`The current version is ${version}`)
LogHelper.info(`The current time zone is ${DateHelper.getTimeZone()}`)
const sLogger = !HAS_LOGGER ? 'disabled' : 'enabled'
LogHelper.info(`Collaborative logger ${sLogger}`)
// await addProvider('1')
await this.bootstrap()
}
/**
* Bootstrap API
*/
private async bootstrap(): Promise<void> {
// Render the web app
this.fastify.register(fastifyStatic, {
root: join(process.cwd(), 'app/dist'),
prefix: '/'
})
this.fastify.get('/', (_request, reply) => {
reply.sendFile('index.html')
})
this.fastify.register(infoPlugin, { apiVersion: API_VERSION })
this.fastify.register(downloadsPlugin, { apiVersion: API_VERSION })
// TODO: HTTP API
/*if (HAS_OVER_HTTP) {
server.fastify.register((instance, opts, next) => {
instance.addHook('preHandler', keyMidd)
instance.post('/api/query', async (request, reply) => {
const { utterance } = request.body
try {
const data = await mainProvider.nlu.process(utterance, { mute: true })
reply.send({
...data,
success: true
})
} catch (e) {
reply.statusCode = 500
reply.send({
message: e.message,
success: false
})
}
})
server.generateSkillsRoutes(instance)
next()
})
}*/
try {
await this.listen()
} catch (e) {
// TODO: remove ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
LogHelper.error(e.message)
}
}
/**
* Launch server
*/
private async listen(): Promise<void> {
const io = IS_DEVELOPMENT_ENV
? new SocketIOServer(this.httpServer, {
cors: { origin: `${this.host}:3000` }
})
: new SocketIOServer(this.httpServer)
// TODO: instanciate new socket server
io.on('connection', server.handleOnConnection)
this.fastify.listen(
{
port: this.port,
host: '0.0.0.0'
},
() => {
LogHelper.title('Initialization')
LogHelper.success(`Server is available at ${this.host}:${this.port}`)
}
)
}
}

View File

@ -1,5 +1,6 @@
import { TCP_SERVER_HOST, TCP_SERVER_PORT } from '@/constants'
import { HOST, PORT, TCP_SERVER_HOST, TCP_SERVER_PORT } from '@/constants'
import TCPClient from '@/core/tcp-client'
import HTTPServer from '@/core/http-server/http-server'
/**
* Register core singletons
@ -9,3 +10,5 @@ export const TCP_CLIENT = new TCPClient(
String(TCP_SERVER_HOST),
TCP_SERVER_PORT
)
export const HTTP_SERVER = new HTTPServer(String(HOST), PORT)

View File

@ -5,9 +5,8 @@ import {
LANG as LEON_LANG,
TCP_SERVER_BIN_PATH
} from '@/constants'
import { TCP_CLIENT } from '@/core'
import { TCP_CLIENT, HTTP_SERVER } from '@/core'
import { LangHelper } from '@/helpers/lang-helper'
import server from '@/core/http-server/server'
;(async (): Promise<void> => {
process.title = 'leon'
@ -23,6 +22,18 @@ import server from '@/core/http-server/server'
// Connect the TCP client to the TCP server
TCP_CLIENT.connect()
// Start the core server
await server.init()
// Start the HTTP server
// await server.init()
await HTTP_SERVER.init()
// TODO
// Register HTTP API endpoints
// await HTTP_API.register()
// TODO
// const httpServer = HTTP_SERVER.httpServer
// TODO
// Start the socket server
// SOCKET_SERVER.init(httpServer)
})()

View File

@ -28,7 +28,7 @@ parser.init = (args) => {
if (!fs.existsSync(args.model)) {
LogHelper.error(
`Cannot find ${args.model}. You can setup the offline STT by running: "npm run setup:offline-stt"`
`Cannot find ${args.model}. You can set up the offline STT by running: "npm run setup:offline-stt"`
)
return false

View File

@ -35,7 +35,7 @@ synthesizer.init = (lang) => {
/* istanbul ignore if */
if (!fs.existsSync(flitePath)) {
LogHelper.error(
`Cannot find ${flitePath} You can setup the offline TTS by running: "npm run setup:offline-tts"`
`Cannot find ${flitePath} You can set up the offline TTS by running: "npm run setup:offline-tts"`
)
return false
}

View File

@ -121,4 +121,4 @@ def raise_connection_error(response):
elif response == "KO - ELEM LIST IS EMPTY" or response == "WARN - NO QUESTION":
raise AkiNoQuestions("\"Akinator.step\" reached 79. No more questions")
else:
raise AkiConnectionFailure("An unknown error has occured. Server response: {}".format(response))
raise AkiConnectionFailure("An unknown error has occured. HttpServer response: {}".format(response))

View File

@ -168,7 +168,7 @@ def get_all():
'GraphQL',
'Graphviz (DOT)',
'Groovy',
'Groovy Server Pages',
'Groovy HttpServer Pages',
'Hack',
'Haml',
'Handlebars',
@ -205,7 +205,7 @@ def get_all():
'Jasmin',
'Java',
'Java Properties',
'Java Server Pages',
'Java HttpServer Pages',
'JavaScript',
'JFlex',
'Jison',

View File

@ -323,7 +323,7 @@ class ServersRetrievalError(SpeedtestHTTPError):
class InvalidServerIDType(SpeedtestException):
"""Server ID used for filtering was not an integer"""
"""HttpServer ID used for filtering was not an integer"""
class NoMatchedServers(SpeedtestException):
@ -335,7 +335,7 @@ class SpeedtestMiniConnectFailure(SpeedtestException):
class InvalidSpeedtestMiniServer(SpeedtestException):
"""Server provided as a speedtest mini server does not actually appear
"""HttpServer provided as a speedtest mini server does not actually appear
to be a speedtest mini server
"""
@ -1042,7 +1042,7 @@ class SpeedtestResults(object):
def csv_header(delimiter=','):
"""Return CSV Headers"""
row = ['Server ID', 'Sponsor', 'Server Name', 'Timestamp', 'Distance',
row = ['HttpServer ID', 'Sponsor', 'HttpServer Name', 'Timestamp', 'Distance',
'Ping', 'Download', 'Upload', 'Share', 'IP Address']
out = StringIO()
writer = csv.writer(out, delimiter=delimiter, lineterminator='')
@ -1394,7 +1394,7 @@ class Speedtest(object):
extension = [ext]
break
if not urlparts or not extension:
raise InvalidSpeedtestMiniServer('Invalid Speedtest Mini Server: '
raise InvalidSpeedtestMiniServer('Invalid Speedtest Mini HttpServer: '
'%s' % server)
self.servers = [{
@ -1501,7 +1501,7 @@ class Speedtest(object):
self.results.server = best
self._best.update(best)
printer('Best Server:\n%r' % best, debug=True)
printer('Best HttpServer:\n%r' % best, debug=True)
return best
def download(self, callback=do_nothing, threads=None):

View File

@ -1,6 +1,6 @@
import axios from 'axios'
import server from '@/core/http-server/server'
import server from '@/core/http-server/http-server'
const urlPrefix = `${process.env.LEON_HOST}:${process.env.LEON_PORT}/api`
const queryUrl = `${urlPrefix}/query`

View File

@ -1,6 +1,6 @@
import { EventEmitter } from 'node:events'
import server from '@/core/http-server/server'
import server from '@/core/http-server/http-server'
describe('server', () => {
describe('init()', () => {