Commit 5cf5f607 authored by mud rz's avatar mud rz
Browse files

hapi bench

parent e9ed1706
......@@ -9,18 +9,19 @@ Start with the OCaml dir since there are some steps present only there - the DB
You can then run:
html only:
```
wrk -t8 -c400 -d30s http://localhost:3000/
wrk -t8 -c400 -d30s http://localhost:3001/
```
db access + html:
```
wrk -t8 -c400 -d30s http://localhost:3000/fortunes
wrk -t8 -c400 -d30s http://localhost:3001/fortunes
```
db access + json:
```
wrk -t8 -c400 -d30s http://localhost:3000/fortunes-json
wrk -t8 -c400 -d30s http://localhost:3001/fortunes-json
```
The ports are:
- 3000 - OCaml
- 3001 - JS
- 3002 - F#
- 3001 - JS fastify
- 3002 - JS hapi
- 4001 - OCaml
- 5001 - F#
......@@ -102,6 +102,18 @@ fastify.get('/fortunes-json', {
.send({ excerpts: fortunes });
})
fastify.get('/fortunes-json2', async (req, reply) => {
const fortunes = await excerptsByAuthor('kan')
fortunes.push({ author: 'kan', excerpt: 'My excerpt', source: 'my source', page: '23' });
fortunes.sort((a, b) => a.excerpt.localeCompare(b.excerpt));
return reply
.header("Content-Type", "application/json")
.code(200)
.send({ excerpts: fortunes });
})
const start = async () => {
try {
const server = fastify.listen(3001)
......
......@@ -7,20 +7,34 @@ open FSharp.Control.Tasks.V2
let url = "localhost"
let port = 5432
let database = "opi"
let connection_uri = Printf.sprintf "Host=%s;Port=%i;Database=%s;Maximum Pool Size=%i" url port database 10
let connection_uri =
Printf.sprintf "Host=%s;Port=%i;Database=%s;Maximum Pool Size=%i" url port database 10
let inline (=>) a b = a, box b
let fortunes () = task {
use conn = new NpgsqlConnection(connection_uri)
let sql = """
let fortunes () =
task {
use conn = new NpgsqlConnection(connection_uri)
let sql = """
SELECT author, excerpt, source, page
FROM excerpts
WHERE author = @author;
"""
let! data = conn.QueryAsync<Excerpt.t>(sql, dict ["author" => "kan"])
let excerpts = List.ofSeq data
let excerpt: Excerpt.t = { author= "kan"; excerpt= "My excerpt"; source= "my source"; page= Some "23" }
let sorted = excerpt::excerpts |> List.sortBy (fun (a: Excerpt.t) -> a.excerpt)
return sorted
}
let! data = conn.QueryAsync<Excerpt.t>(sql, dict [ "author" => "kan" ])
let excerpts = List.ofSeq data
let excerpt: Excerpt.t =
{ author = "kan"
excerpt = "My excerpt"
source = "my source"
page = Some "23" }
let sorted =
excerpt
:: excerpts
|> List.sortBy (fun (a: Excerpt.t) -> a.excerpt)
return sorted
}
......@@ -11,92 +11,93 @@ open Microsoft.Extensions.DependencyInjection
open Giraffe
open FSharp.Control.Tasks.V2.ContextInsensitive
let indexHandler: HttpHandler =
let view = Content.welcome ()
type hello_world = { hello: string }
let indexHandler: HttpHandler = json { hello = "world" }
let rootHandler: HttpHandler =
let view = Content.welcome ()
htmlView view
let fortunesHandler: HttpHandler =
fun ctx next -> task {
let! excerpts = Db.fortunes ()
let view = Content.excerpts_listing_page excerpts
return! htmlView view ctx next
}
fun ctx next ->
task {
let! excerpts = Db.fortunes ()
let view = Content.excerpts_listing_page excerpts
return! htmlView view ctx next
}
[<CLIMutable>]
type res = { excerpts: Excerpt.t list }
let fortunesJsonHandler: HttpHandler =
fun ctx next -> task {
let! excerpts = Db.fortunes ()
let res= { excerpts= excerpts }
return! json res ctx next
}
fun ctx next ->
task {
let! excerpts = Db.fortunes ()
let res = { excerpts = excerpts }
return! json res ctx next
}
let webApp =
choose [
GET >=>
choose [
route "/" >=> indexHandler
route "/fortunes" >=> fortunesHandler
route "/fortunes-json" >=> fortunesJsonHandler
]
setStatusCode 404 >=> text "Not Found" ]
choose [ GET
>=> choose [ route "/" >=> indexHandler
route "/root" >=> rootHandler
route "/fortunes" >=> fortunesHandler
route "/fortunes-json" >=> fortunesJsonHandler ]
setStatusCode 404 >=> text "Not Found" ]
// ---------------------------------
// Error handler
// ---------------------------------
let errorHandler (ex : Exception) (logger : ILogger) =
let errorHandler (ex: Exception) (logger: ILogger) =
logger.LogError(ex, "An unhandled exception has occurred while executing the request.")
clearResponse >=> setStatusCode 500 >=> text ex.Message
clearResponse
>=> setStatusCode 500
>=> text ex.Message
// ---------------------------------
// Config and Main
// ---------------------------------
let configureCors (builder : CorsPolicyBuilder) =
builder.WithOrigins("http://localhost:8080")
.AllowAnyMethod()
.AllowAnyHeader()
|> ignore
let configureCors (builder: CorsPolicyBuilder) =
builder.WithOrigins("http://localhost:8080").AllowAnyMethod().AllowAnyHeader()
|> ignore
let configureApp (app: IApplicationBuilder) =
let env =
app.ApplicationServices.GetService<IWebHostEnvironment>()
let configureApp (app : IApplicationBuilder) =
let env = app.ApplicationServices.GetService<IWebHostEnvironment>()
(match env.EnvironmentName with
| "Development" -> app.UseDeveloperExceptionPage()
| _ -> app.UseGiraffeErrorHandler(errorHandler))
.UseHttpsRedirection()
.UseCors(configureCors)
.UseStaticFiles()
| "Development" -> app.UseDeveloperExceptionPage()
| _ -> app.UseGiraffeErrorHandler(errorHandler)).UseHttpsRedirection().UseCors(configureCors).UseStaticFiles()
.UseGiraffe(webApp)
let configureServices (services : IServiceCollection) =
services.AddCors() |> ignore
let configureServices (services: IServiceCollection) =
services.AddCors() |> ignore
services.AddGiraffe() |> ignore
let configureLogging (builder : ILoggingBuilder) =
builder.AddFilter(fun l -> l.Equals LogLevel.Error)
.AddConsole()
.AddDebug() |> ignore
let configureLogging (builder: ILoggingBuilder) =
builder.AddFilter(fun l -> l.Equals LogLevel.Error).AddConsole().AddDebug()
|> ignore
[<EntryPoint>]
let main args =
let contentRoot = Directory.GetCurrentDirectory()
let webRoot = Path.Combine(contentRoot, "WebRoot")
let webRoot = Path.Combine(contentRoot, "WebRoot")
Console.WriteLine("Starting...")
let port = 5001
let port = 3000
let port = string port
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(
fun webHostBuilder ->
webHostBuilder
.UseContentRoot(contentRoot)
.UseWebRoot(webRoot)
.UseUrls("http://localhost:3002/")
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureServices(configureServices)
.ConfigureLogging(configureLogging)
|> ignore
Console.WriteLine("Giraffe listening on :3002.")
)
.Build()
.Run()
.ConfigureWebHostDefaults(fun webHostBuilder ->
webHostBuilder.UseContentRoot(contentRoot).UseWebRoot(webRoot).UseUrls("http://localhost:" + port)
.Configure(Action<IApplicationBuilder> configureApp).ConfigureServices(configureServices)
.ConfigureLogging(configureLogging)
|> ignore
Console.WriteLine("Giraffe listening on :" + port)).Build().Run()
0
.PHONY: install run
init: install
install:
npm install
# build the app in development mode and watch it for changes
run:
npm run start
This diff is collapsed.
{
"name": "fastify-bench",
"private": true,
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"@hapi/hapi": "^20.0.1",
"@hapi/vision": "^6.0.1",
"@types/hapi": "^18.0.3",
"fast-json-stringify": "^2.2.7",
"knex": "^0.21.5",
"mustache": "^4.0.1",
"nodemon": "^2.0.4",
"pg": "^8.3.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"single": "nodemon src/server.js",
"start": "nodemon src/index.js"
},
"author": "",
"license": "ISC"
}
const cluster = require("cluster");
const numCPUs = require("os").cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
console.log("Master starting " + new Date().toISOString());
cluster.on("exit", () => {
process.exit(1);
});
} else {
// worker task
require("./server");
}
const path = require('path')
const Hapi = require('@hapi/hapi');
const fastJson = require('fast-json-stringify')
const home = (req, reply) => {
return reply.view('welcome.mustache')
}
const stringifyMessage = fastJson({
type: 'object',
properties: {
message: { type: 'string' }
}
})
const handleJson = (_req, reply) => {
const res = stringifyMessage({ message: "Hello, World!" });
return reply
.response(res)
.header("Content-Type", "application/json")
}
const knex = require("knex")({
client: "pg",
connection: {
host: "localhost",
password: "",
database: "opi"
},
});
function excerptsByAuthor(author) {
return knex("excerpts").select("*").where({ author });
}
const EXCERPTS_SCHEMA = {
"title": "Example Schema",
"type": "object",
"properties": {
"excerpts": {
"type": "array",
"items": {
"minimum": 0,
"type": "object",
"properties": {
"author": { "type": "string" },
"excerpt": { "type": "string" },
"source": { "type": "string" },
"page": { "type": "string" },
},
"additionalProperties": false,
"required": ["author", "excerpt", "source"]
},
}
},
"additionalProperties": false,
"required": ["excerpts"]
}
const fortunesToString = fastJson(EXCERPTS_SCHEMA);
const fortunesJson = async (_req, reply) => {
const fortunes = await excerptsByAuthor('kan')
fortunes.push({ author: 'kan', excerpt: 'My excerpt', source: 'my source', page: '23' });
fortunes.sort((a, b) => a.excerpt.localeCompare(b.excerpt));
const res = fortunesToString({ excerpts: fortunes });
return reply
.response(res)
.header("Content-Type", "application/json")
}
const fortunesJson2 = async (_req, _reply) => {
const fortunes = await excerptsByAuthor('kan')
fortunes.push({ author: 'kan', excerpt: 'My excerpt', source: 'my source', page: '23' });
fortunes.sort((a, b) => a.excerpt.localeCompare(b.excerpt));
return { excerpts: fortunes }
}
const fortunes = async (_req, reply) => {
const fortunes = await excerptsByAuthor('kan')
fortunes.push({ author: 'kan', excerpt: 'My excerpt', source: 'my source', page: '23' });
fortunes.sort((a, b) => a.excerpt.localeCompare(b.excerpt));
return reply.view('fortunes.mustache', { excerpts: fortunes })
}
const Mustache = require('mustache')
const start = async () => {
const server = Hapi.server({
port: 3002,
host: 'localhost'
});
await server.register(require('@hapi/vision'));
const partials = {};
server.views({
engines: {
mustache: {
compile: function (template) {
Mustache.parse(template);
return function (context) {
return Mustache.render(template, context, partials);
};
},
registerPartial: function (name, src) {
partials[name] = src;
}
}
},
relativeTo: __dirname,
path: 'templates',
partialsPath: 'templates',
});
server.route({
method: 'GET',
path: '/',
handler: home
});
server.route({
method: 'GET',
path: '/json',
handler: handleJson,
});
server.route({
method: 'GET',
path: '/fortunes-json',
handler: fortunesJson,
});
server.route({
method: 'GET',
path: '/fortunes-json2',
handler: fortunesJson2,
});
server.route({
method: 'GET',
path: '/fortunes',
handler: fortunes,
});
await server.start();
console.log('Server running on %s', server.info.uri);
}
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
start()
/* Style taken from https://www.w3schools.com/howto/howto_css_contact_form.asp */
/* Style inputs with type="text", select elements and textareas */
input[type=text], select, textarea {
width: 100%; /* Full width */
padding: 12px; /* Some padding */
border: 1px solid #ccc; /* Gray border */
border-radius: 4px; /* Rounded borders */
box-sizing: border-box; /* Make sure that padding and width stays in place */
margin-top: 6px; /* Add a top margin */
margin-bottom: 16px; /* Bottom margin */
resize: vertical /* Allow the user to vertically resize the textarea (not horizontally) */
}
/* Style the submit button with a specific background color etc */
input[type=submit] {
background-color: #4CAF50;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* When moving the mouse over the submit button, add a darker green color */
input[type=submit]:hover {
background-color: #45a049;
}
/* Add a background color and some padding around the form */
.container {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
{{> header}}
<h1>Excerpts</h1>
{{#excerpts}}
<blockquote class="excerpt">
<p>{{excerpt}}</p>
<p>-- {{author}} ({{source}}, {{page}})</p>
</blockquote>
{{/excerpts}}
{{> footer}}
<!DOCTYPE HTML>
<html>
<head>
<title>OCaml Webapp Tutorial</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
{{> header}}
<h1>Fastify Webapp Tutorial</h1>
<h2>Hello</h2>
<ul>
<li><a href="/hello">hiya</a></li>
<li><a href="/hello/中文">中文</a></li>
<li><a href="/hello/Deutsch">Deutsch</a></li>
<li><a href="/hello/English">English</a></li>
</ul>
<h2>Excerpts</h2>
<ul>
<li><a href="/excerpts/add">Add Excerpt</a></li>
<li><a href="/excerpts">Excerpts</a></li>
</ul>
{{> footer}}
......@@ -6,7 +6,7 @@ let static =
~uri_prefix:"/static"
()
let port = 3000
let port = 4001
(** Build the Opium app *)
let app : Opium.App.t =
App.empty
......
......@@ -5,7 +5,7 @@ open Tyxml
(** The route handlers for our app *)
(** Defines a handler that replies to GET requests at the root endpoint *)
let root = get "/"
let root = get "/root"
begin fun _req ->
let res = Response.of_html Content.welcome_page in
Lwt.return res
......@@ -97,6 +97,17 @@ let fortunes_json = get "/fortunes-json" begin fun req ->
Lwt.return res
end
module Hello_world = struct
type t =
{ hello: string
}[@@deriving yojson]
let hello_world = get "/" begin fun _req ->
let res = Response.of_json (to_yojson { hello= "world" }) in
Lwt.return res
end
end
let excerpts = get "/excerpts" begin fun req ->
let open Lwt.Syntax in
let+ authors = Db.Get.authors req in
......@@ -104,7 +115,8 @@ let excerpts = get "/excerpts" begin fun req ->
end
let routes =
[ root
[ Hello_world.hello_world
; root
; hello
; hello_fallback
; excerpts
......
......@@ -21,7 +21,6 @@ depends: [
"fmt" {>= "0.8.8"}
"logs" {>= "0.7.0"}
"archi-lwt" {>= "0.1.0"}
"reweb" {dev}
"utop" {dev}