use fastify

main
qwertyforce 2021-08-21 21:51:40 +03:00
parent 17eefa24ad
commit 99dc3adb05
7 changed files with 1285 additions and 1307 deletions

2098
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc --project tsconfig.server.json",
"build_server": "tsc --project tsconfig.server.json",
"start": "cross-env NODE_ENV=production node dist/server/index.js"
},
@ -18,15 +19,14 @@
},
"homepage": "https://github.com/qwertyforce/ambience#readme",
"dependencies": {
"@types/express": "^4.17.11",
"@types/http-proxy": "^1.17.6",
"@types/multer": "^1.4.5",
"@types/node": "^15.3.0",
"axios": "^0.21.1",
"cross-env": "^7.0.3",
"express": "^4.17.1",
"fastify": "^3.20.1",
"fastify-formbody": "^5.1.0",
"fastify-multipart": "^4.0.7",
"fastify-reply-from": "^6.0.1",
"form-data": "^4.0.0",
"http-proxy": "^1.18.1",
"multer": "^1.4.2"
"json-schema-to-ts": "^1.6.4"
}
}

142
server/helpers/image_ops.ts Normal file
View File

@ -0,0 +1,142 @@
import config from "../../config/config"
import FormData from 'form-data'
import axios from 'axios'
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
async function calculate_phash_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.phash_microservice_url}/calculate_phash_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function calculate_akaze_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.akaze_microservice_url}/calculate_akaze_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function calculate_nn_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.nn_microservice_url}/calculate_nn_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function calculate_hist_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.hist_microservice_url}/calculate_hist_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function phash_reverse_search(image: Buffer) {
try {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
const status = await axios.post(`${config.phash_microservice_url}/phash_reverse_search`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
} catch (err) {
console.log(err)
return []
}
}
async function akaze_reverse_search(image: Buffer) {
try {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
const status = await axios.post(`${config.akaze_microservice_url}/akaze_reverse_search`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
} catch (err) {
console.log(err)
return []
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
async function delete_akaze_features_by_id(image_id: number) {
const status = await axios.post(`${config.akaze_microservice_url}/delete_akaze_features`, { image_id: image_id })
return status.data
}
async function delete_nn_features_by_id(image_id: number) {
const status = await axios.post(`${config.nn_microservice_url}/delete_nn_features`, { image_id: image_id })
return status.data
}
async function delete_hist_features_by_id(image_id: number) {
const status = await axios.post(`${config.hist_microservice_url}/delete_hist_features`, { image_id: image_id })
return status.data
}
async function delete_phash_features_by_id(image_id: number) {
const status = await axios.post(`${config.phash_microservice_url}/delete_phash_features`, { image_id: image_id })
return status.data
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
async function calculate_all_image_features(image_id: number, image_buffer: Buffer) {
return Promise.allSettled([
calculate_akaze_features(image_id, image_buffer),
calculate_nn_features(image_id, image_buffer),
calculate_hist_features(image_id, image_buffer),
calculate_phash_features(image_id, image_buffer),
])
}
async function delete_all_image_features(image_id: number) {
return Promise.allSettled([
delete_akaze_features_by_id(image_id),
delete_nn_features_by_id(image_id),
delete_hist_features_by_id(image_id),
delete_phash_features_by_id(image_id)
])
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default { calculate_all_image_features, delete_all_image_features, phash_reverse_search, akaze_reverse_search }

View File

@ -1,224 +1,76 @@
import express from 'express'
import { Request, Response } from 'express';
import bodyParser from 'body-parser';
import multer from 'multer'
import config from './../config/config'
import httpProxy from 'http-proxy'
import axios from 'axios'
import FormData from 'form-data'
const app = express()
// app.use(bodyParser.json())
app.disable('x-powered-by')
const apiProxy = httpProxy.createProxyServer()
import fastifyMultipart from 'fastify-multipart'
import fastify from 'fastify'
import formBodyPlugin from 'fastify-formbody'
import fastifyReplyFrom from 'fastify-reply-from'
const server = fastify()
server.register(formBodyPlugin)
server.register(fastifyReplyFrom)
const port = config.server_port
const storage = multer.memoryStorage()
const upload_100MB = multer({ storage: storage, limits: { files: 1, fileSize: 100000000 } }) //100MB
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
async function calculate_phash_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.phash_microservice_url}/calculate_phash_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function calculate_akaze_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.akaze_microservice_url}/calculate_akaze_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function calculate_nn_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.nn_microservice_url}/calculate_nn_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function calculate_hist_features(image_id: number, image: Buffer) {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
form.append('image_id', image_id.toString())
const status = await axios.post(`${config.hist_microservice_url}/calculate_hist_features`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
}
async function phash_reverse_search(image: Buffer) {
try {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
const status = await axios.post(`${config.phash_microservice_url}/phash_reverse_search`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
} catch (err) {
console.log(err)
return []
server.register(fastifyMultipart, {
attachFieldsToBody: true,
sharedSchemaId: '#mySharedSchema',
limits: {
fieldNameSize: 100, // Max field name size in bytes
fieldSize: 1000, // Max field value size in bytes
fields: 10, // Max number of non-file fields
fileSize: 50000000, // For multipart forms, the max file size in bytes //50MB
files: 1, // Max number of file fields
headerPairs: 2000 // Max number of header key=>value pairs
}
}
});
async function akaze_reverse_search(image: Buffer) {
try {
const form = new FormData();
form.append('image', image, { filename: 'document' }) //hack to make nodejs buffer work with form-data
const status = await axios.post(`${config.akaze_microservice_url}/akaze_reverse_search`, form.getBuffer(), {
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
...form.getHeaders()
}
})
return status.data
} catch (err) {
console.log(err)
return []
}
}
import calculate_all_image_features from "./routes/calculate_all_image_features"
import delete_all_image_features from "./routes/delete_all_image_features"
import reverse_search from "./routes/reverse_search"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
async function delete_akaze_features_by_id(image_id: number) {
const status = await axios.post(`${config.akaze_microservice_url}/delete_akaze_features`, { image_id: image_id })
return status.data
}
async function delete_nn_features_by_id(image_id: number) {
const status = await axios.post(`${config.nn_microservice_url}/delete_nn_features`, { image_id: image_id })
return status.data
}
async function delete_hist_features_by_id(image_id: number) {
const status = await axios.post(`${config.hist_microservice_url}/delete_hist_features`, { image_id: image_id })
return status.data
}
async function delete_phash_features_by_id(image_id: number) {
const status = await axios.post(`${config.phash_microservice_url}/delete_phash_features`, { image_id: image_id })
return status.data
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
async function calculate_all_image_features(image_id: number, image_buffer: Buffer) {
return Promise.allSettled([
calculate_akaze_features(image_id, image_buffer),
calculate_nn_features(image_id, image_buffer),
calculate_hist_features(image_id, image_buffer),
calculate_phash_features(image_id, image_buffer),
])
}
async function delete_image_features(image_id: number) {
return Promise.allSettled([
delete_akaze_features_by_id(image_id),
delete_nn_features_by_id(image_id),
delete_hist_features_by_id(image_id),
delete_phash_features_by_id(image_id)
])
}
///////////////////////////////////////////////////////////////////////////////////////////////ALL
app.post('/calculate_all_image_features', [upload_100MB.single('image')], async (req: Request, res: Response) => {
const image_id = parseInt(req.body.image_id)
if (req.file && typeof image_id === "number") {
const results = await calculate_all_image_features(image_id, req.file.buffer)
console.log(results)
res.send(results)
} else {
return res.sendStatus(403)
}
})
app.post('/delete_all_image_features',[bodyParser.json()], async (req: Request, res: Response) => {
const image_id = parseInt(req.body.image_id)
if (typeof image_id === "number") {
const results = await delete_image_features(image_id)
console.log(results)
res.send(results)
} else {
return res.sendStatus(403)
}
})
app.post('/reverse_search', [upload_100MB.single('image')], async (req: Request, res: Response) => {
if (req.file) {
const phash_found=await phash_reverse_search(req.file.buffer)
if(phash_found.length!==0){
res.json(phash_found)
}else{
const akaze_found=await akaze_reverse_search(req.file.buffer)
res.json(akaze_found)
}
} else {
return res.sendStatus(403)
}
})
///////////////////////////////////////////////////////////////////////////////////////////////
server.post("/calculate_all_image_features",calculate_all_image_features)
server.post("/delete_all_image_features",delete_all_image_features)
server.post("/reverse_search",reverse_search)
///////////////////////////////////////////////////////////////////////////////////////////////PROXY
app.post(['/akaze_reverse_search','/calculate_akaze_features','/delete_akaze_features'], async (req, res) => {
const akaze_routes = ['/akaze_reverse_search', '/calculate_akaze_features', '/delete_akaze_features']
akaze_routes.forEach((r) => server.post(r, async (_req, res) => {
try {
apiProxy.web(req, res, { target: config.akaze_microservice_url });
res.from(config.akaze_microservice_url)
} catch (err) {
res.status(500).send('Akaze microservice is down')
}
})
}))
app.post(['/nn_get_similar_images_by_image_buffer','/nn_get_similar_images_by_text',
'/nn_get_similar_images_by_id','/calculate_nn_features','/delete_nn_features','/nn_get_image_tags_by_image_buffer'], async (req, res) => {
const nn_routes = ['/nn_get_similar_images_by_image_buffer', '/nn_get_similar_images_by_text',
'/nn_get_similar_images_by_id', '/calculate_nn_features', '/delete_nn_features', '/nn_get_image_tags_by_image_buffer']
nn_routes.forEach((r) => server.post(r, async (_req, res) => {
try {
apiProxy.web(req, res, { target: config.nn_microservice_url });
res.from(config.nn_microservice_url)
} catch (err) {
res.status(500).send('NN microservice is down')
}
})
app.post(['/hist_get_similar_images_by_image_buffer','/hist_get_similar_images_by_id','/calculate_hist_features','/delete_hist_features'], async (req, res) => {
}))
const hist_routes = ['/hist_get_similar_images_by_image_buffer', '/hist_get_similar_images_by_id', '/calculate_hist_features', '/delete_hist_features']
hist_routes.forEach((r) => server.post(r, async (_req, res) => {
try {
apiProxy.web(req, res, { target: config.hist_microservice_url });
res.from(config.hist_microservice_url)
} catch (err) {
res.status(500).send('HIST microservice is down')
}
})
}))
app.post(['/phash_reverse_search','/calculate_phash_features','/delete_phash_features'], async (req, res) => {
const phash_routes = ['/phash_reverse_search', '/calculate_phash_features', '/delete_phash_features']
phash_routes.forEach((r) => server.post(r, async (_req, res) => {
try {
apiProxy.web(req, res, { target: config.phash_microservice_url });
res.from(config.phash_microservice_url)
} catch (err) {
res.status(500).send('Phash microservice is down')
}
})
}))
////////////////////////////////////////////////////////////////////////////////////////////////////////////
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
server.listen(port, "127.0.0.1", function (err, address) {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`server listening on ${address}`)
})

View File

@ -0,0 +1,38 @@
import { FastifyRequest, FastifyReply } from "fastify"
import { FromSchema } from "json-schema-to-ts";
import image_ops from "./../helpers/image_ops"
const body_schema_calculate_all_image_features = {
type: 'object',
properties: {
image: { $ref: '#mySharedSchema' },
image_id: {
type: "object",
properties: {
value: { type: 'string' }
}
},
},
required: ['image', 'image_id'],
} as const;
async function calculate_all_image_features(req: FastifyRequest<{ Body: FromSchema<typeof body_schema_calculate_all_image_features> }>, res: FastifyReply) {
let image_buffer: Buffer;
try {
image_buffer = await (req as any).body.image.toBuffer()
} catch (err) {
return res.status(500).send()
}
if (req.body.image_id.value) {
const image_id = parseInt(req.body.image_id.value)
const results = await image_ops.calculate_all_image_features(image_id, image_buffer)
console.log(results)
res.send(results)
}
}
export default {
schema: {
body: body_schema_calculate_all_image_features
},
handler: calculate_all_image_features
}

View File

@ -0,0 +1,23 @@
import { FastifyRequest, FastifyReply } from "fastify"
import { FromSchema } from "json-schema-to-ts";
import image_ops from "./../helpers/image_ops"
const body_schema_delete_all_image_features = {
type: 'object',
properties: {
image_id: { type: "number" }
},
required: ['image_id'],
} as const;
async function delete_all_image_features(req: FastifyRequest<{ Body: FromSchema<typeof body_schema_delete_all_image_features> }>, res: FastifyReply) {
const results = await image_ops.delete_all_image_features(req.body.image_id)
console.log(results)
res.send(results)
}
export default {
schema: {
body: body_schema_delete_all_image_features
},
handler: delete_all_image_features
}

View File

@ -0,0 +1,33 @@
import { FastifyRequest, FastifyReply } from "fastify"
import { FromSchema } from "json-schema-to-ts";
import image_ops from "./../helpers/image_ops"
const body_schema_reverse_search = {
type: 'object',
properties: {
image: { $ref: '#mySharedSchema' },
},
required: ['image'],
} as const;
async function reverse_search(req: FastifyRequest<{ Body: FromSchema<typeof body_schema_reverse_search> }>, res: FastifyReply) {
let image_buffer: Buffer;
try {
image_buffer = await (req as any).body.image.toBuffer()
} catch (err) {
return res.status(500).send()
}
const phash_found = await image_ops.phash_reverse_search(image_buffer)
if (phash_found.length !== 0) {
res.send(phash_found)
} else {
const akaze_found = await image_ops.akaze_reverse_search(image_buffer)
res.send(akaze_found)
}
}
export default {
schema: {
body: body_schema_reverse_search
},
handler: reverse_search
}