├── README.md ├── body.json ├── chart.png ├── chart2.png ├── colors.json ├── go.docker ├── java ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── rest │ ├── Application.java │ ├── Body.java │ └── Controller.java ├── node.docker ├── post.lua ├── server.go └── server.js /README.md: -------------------------------------------------------------------------------- 1 | # REST Server Performance 2 | 3 | ## Goals 4 | Compare a minimal REST service performance in Node.js, Go and Java. The focus is only on HTTP & JSON as these are most common for REST APIs. 5 | 6 | The goal is to compare performance of the standard libraries that come out of the box, as this is what most projects use in practice. No external libraries are used, just the builtin APIs (except for Java). 7 | 8 | ## Setup 9 | 10 | All the servers do the same - parse the JSON body and return it back in the response. 11 | 12 | Install 13 | - Node.js 14 | - Go 15 | - Java 16 | - Docker 17 | - [wrk](https://github.com/wg/wrk) 18 | 19 | The POST request is defined in [post.lua](post.lua). 20 | The content is ~4KB JSON. 21 | 22 | ``` 23 | $ node -v 24 | v10.1.0 25 | 26 | $ go version 27 | go version go1.10.1 linux/amd64 28 | 29 | $ java -version 30 | java version "11.0.2" 2019-01-15 LTS 31 | Java(TM) SE Runtime Environment 18.9 (build 11.0.2+9-LTS) 32 | Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.2+9-LTS, mixed mode) 33 | 34 | $ cat /etc/issue 35 | Ubuntu 16.04.4 LTS \n \l 36 | ``` 37 | CPU: Intel® Core™ i7-7500U CPU @ 2.70GHz × 4 38 | 39 | RAM: 16GB 40 | 41 | ## Single thread 42 | 43 | ### Node.js 44 | Start the server 45 | ``` 46 | $ node server.js 47 | Listening on port 3000 48 | ``` 49 | Run the test in a separate console 50 | ``` 51 | $ wrk -d 20s -s post.lua http://localhost:3000 52 | Running 20s test @ http://localhost:3000 53 | 2 threads and 10 connections 54 | Thread Stats Avg Stdev Max +/- Stdev 55 | Latency 793.29us 221.36us 11.96ms 92.88% 56 | Req/Sec 6.36k 513.96 12.33k 83.04% 57 | 253629 requests in 20.10s, 564.55MB read 58 | Requests/sec: 12618.86 59 | Transfer/sec: 28.09MB 60 | ``` 61 | During the test the _node_ process runs at ~100% CPU and ~70MB memory. 62 | 63 | ### Go 64 | Start the server 65 | ``` 66 | $ GOMAXPROCS=1 go run server.go 67 | 2018/05/19 23:45:39 Listening on port 3000 68 | ``` 69 | To get comparable results we start the go server with `GOMAXPROCS=1` as Node.js executes JavaScript in a single thread. 70 | 71 | **Note** this limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit. Source https://golang.org/pkg/runtime/. 72 | 73 | Run the test in a separate console 74 | ``` 75 | $ wrk -d 20s -s post.lua http://localhost:3000 76 | Running 20s test @ http://localhost:3000 77 | 2 threads and 10 connections 78 | Thread Stats Avg Stdev Max +/- Stdev 79 | Latency 1.40ms 735.96us 7.03ms 59.42% 80 | Req/Sec 3.59k 249.95 7.36k 93.77% 81 | 143198 requests in 20.10s, 318.06MB read 82 | Requests/sec: 7124.41 83 | Transfer/sec: 15.82MB 84 | ``` 85 | During the test the _server_ process runs at ~100% CPU and ~10MB memory. 86 | 87 | The Go server parses the JSON request into fixed structures. 88 | It is interesting to test what happens when we use unstructured JSON parsing with `interface{}`. So, open [server.go](server.go) and change this line 89 | ``` 90 | var body Body 91 | ``` 92 | to 93 | ``` 94 | var body interface{} 95 | ``` 96 | Run again the test 97 | ``` 98 | $ wrk -d 20s -s post.lua http://localhost:3000 99 | Running 20s test @ http://localhost:3000 100 | 2 threads and 10 connections 101 | Thread Stats Avg Stdev Max +/- Stdev 102 | Latency 2.03ms 1.08ms 22.77ms 63.43% 103 | Req/Sec 2.48k 128.59 2.73k 74.00% 104 | 98894 requests in 20.01s, 219.65MB read 105 | Requests/sec: 4943.11 106 | Transfer/sec: 10.98MB 107 | ``` 108 | 109 | ### Results 110 | Surprisingly Node.js is faster at HTTP and JSON handling. Probably the reason is that most of the work is done in the native (C++) parts of node and V8. 111 | 112 | ![chart](chart.png) 113 | 114 | This is a fair comparison as both use almost the same amount of CPU (~100% ±3pp). 115 | 116 | We see also that in Go structured JSON is faster. 117 | You can find more details about this [here](https://github.com/dotchev/go-json-bench). 118 | 119 | ## Single core, unlimited threads 120 | Many people commented that the limiting GOMAXPROCS is unfair to Go, so here we run the test without such limits but limit the actual resources available to the process. 121 | 122 | Here we run each server in a docker container limited to one CPU. 123 | This way we ensure both servers have equal resources. 124 | 125 | ### Node 126 | Build server image 127 | ```sh 128 | docker build -t rest-node -f node.docker . 129 | ``` 130 | Start container and limit it to 1 CPU 131 | ``` 132 | $ docker run -it -p 3000:3000 --cpus=1 --rm rest-node 133 | Node version: v10.1.0 134 | CPU: 4 x Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz 135 | Listening on port 3000 136 | ``` 137 | Run the load test 138 | ``` 139 | $ wrk -d 20s -s post.lua http://localhost:3000 140 | Running 20s test @ http://localhost:3000 141 | 2 threads and 10 connections 142 | Thread Stats Avg Stdev Max +/- Stdev 143 | Latency 1.31ms 1.96ms 59.60ms 97.28% 144 | Req/Sec 4.56k 785.46 5.30k 88.25% 145 | 181603 requests in 20.00s, 404.23MB read 146 | Requests/sec: 9078.68 147 | Transfer/sec: 20.21MB 148 | ``` 149 | 150 | ### Go 151 | Here we test only the faster structured JSON handling so restore server.go 152 | to its original version. 153 | 154 | Build server image 155 | ```sh 156 | docker build -t rest-go -f go.docker . 157 | ``` 158 | Start container and limit it to 1 CPU 159 | ``` 160 | $ docker run -it -p 3000:3000 --cpus=1 --rm rest-go 161 | Version: go1.10.2 162 | NumCPU: 4 163 | GOMAXPROCS: 4 164 | Listening on port 3000 165 | ``` 166 | Run the load test 167 | ``` 168 | $ wrk -d 20s -s post.lua http://localhost:3000 169 | Running 20s test @ http://localhost:3000 170 | 2 threads and 10 connections 171 | Thread Stats Avg Stdev Max +/- Stdev 172 | Latency 25.13ms 25.78ms 179.49ms 77.94% 173 | Req/Sec 291.19 79.29 1.28k 95.50% 174 | 11614 requests in 20.03s, 25.80MB read 175 | Requests/sec: 579.77 176 | Transfer/sec: 1.29MB 177 | ``` 178 | It seems running 4 threads on a single core is not a good idea. 179 | Let's limit the threads to 1. 180 | ``` 181 | $ docker run -it -p 3000:3000 --cpus=1 -e "GOMAXPROCS=1" --rm rest-go 182 | Version: go1.10.2 183 | NumCPU: 4 184 | GOMAXPROCS: 1 185 | Listening on port 3000 186 | ``` 187 | Run again the load test 188 | ``` 189 | $ wrk -d 20s -s post.lua http://localhost:3000 190 | Running 20s test @ http://localhost:3000 191 | 2 threads and 10 connections 192 | Thread Stats Avg Stdev Max +/- Stdev 193 | Latency 1.69ms 1.70ms 58.44ms 98.22% 194 | Req/Sec 3.12k 231.49 3.48k 86.57% 195 | 124807 requests in 20.10s, 277.21MB read 196 | Requests/sec: 6209.90 197 | Transfer/sec: 13.79MB 198 | ``` 199 | ### Results 200 | | | Node.js | Go (unlimited) | Go (GOMAXPROCS=1) 201 | |-----|---------|----------------|------------------ 202 | |Req/s| 9078 | 579 | 6209 203 | 204 | ## All CPU cores, no limits 205 | Here we use all the CPUs on the machine - no limits. 206 | 207 | ### Node.js 208 | Start node cluster with one master process + one worker process per CPU 209 | ``` 210 | $ CLUSTER=1 node server.js 211 | Node version: v10.1.0 212 | CPU: 4 x Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz 213 | Master 23652 is running 214 | Listening on port 3000 215 | Worker 23658 started 216 | Worker 23663 started 217 | Worker 23664 started 218 | Worker 23669 started 219 | ``` 220 | Run the load test 221 | ``` 222 | $ wrk -d 20s -s post.lua http://localhost:3000 223 | Running 20s test @ http://localhost:3000 224 | 2 threads and 10 connections 225 | Thread Stats Avg Stdev Max +/- Stdev 226 | Latency 546.62us 0.97ms 44.91ms 97.55% 227 | Req/Sec 11.04k 1.62k 15.20k 84.50% 228 | 439361 requests in 20.03s, 0.96GB read 229 | Requests/sec: 21938.23 230 | Transfer/sec: 48.83MB 231 | ``` 232 | Here 4 _node_ processes run at ~90% CPU with ~55MB RAM each, 233 | i.e. total of ~360% CPU and ~220MB RAM. 234 | 235 | ### Go 236 | ``` 237 | $ go run server.go 238 | Version: go1.10.1 239 | NumCPU: 4 240 | GOMAXPROCS: 4 241 | Listening on port 3000 242 | ``` 243 | ``` 244 | $ wrk -d 20s -s post.lua http://localhost:3000 245 | Running 20s test @ http://localhost:3000 246 | 2 threads and 10 connections 247 | Thread Stats Avg Stdev Max +/- Stdev 248 | Latency 1.09ms 1.58ms 26.92ms 93.73% 249 | Req/Sec 6.42k 495.44 12.33k 84.04% 250 | 256084 requests in 20.10s, 568.79MB read 251 | Requests/sec: 12740.96 252 | Transfer/sec: 28.30MB 253 | ``` 254 | Here the _server_ process runs at ~360% CPU with ~11MB RAM. 255 | 256 | ### Java (Spring Boot) 257 | Ok, here we do use an additional library - Spring Boot, which seems to be very popular recently. 258 | 259 | Here we add Java just to get a rough idea how it compares against Node and Go. 260 | 261 | ``` 262 | $ cd java 263 | $ gradle build 264 | $ java -jar build/libs/gs-rest-service-0.1.0.jar 265 | ``` 266 | ``` 267 | $ wrk -d 20s -s post.lua http://localhost:8080 268 | Running 20s test @ http://localhost:8080 269 | 2 threads and 10 connections 270 | Thread Stats Avg Stdev Max +/- Stdev 271 | Latency 2.14ms 4.56ms 70.13ms 91.95% 272 | Req/Sec 5.52k 1.94k 11.26k 70.00% 273 | 219991 requests in 20.01s, 501.05MB read 274 | Requests/sec: 10993.65 275 | Transfer/sec: 25.04MB 276 | ``` 277 | Here the _java_ process runs at ~360% CPU with ~450MB memory. 278 | 279 | ### Results 280 | | | Node.js | Go | Java | 281 | |-----|----------|----------|--------| 282 | |Req/s| 21938 | 12740 | 10993 | 283 | 284 | ![chart](chart2.png) 285 | -------------------------------------------------------------------------------- /body.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": "black", 5 | "category": "hue", 6 | "type": "primary", 7 | "code": { 8 | "rgba": [ 9 | 255, 10 | 255, 11 | 255, 12 | 1 13 | ], 14 | "hex": "#000" 15 | } 16 | }, 17 | { 18 | "color": "white", 19 | "category": "value", 20 | "code": { 21 | "rgba": [ 22 | 0, 23 | 0, 24 | 0, 25 | 1 26 | ], 27 | "hex": "#FFF" 28 | } 29 | }, 30 | { 31 | "color": "red", 32 | "category": "hue", 33 | "type": "primary", 34 | "code": { 35 | "rgba": [ 36 | 255, 37 | 0, 38 | 0, 39 | 1 40 | ], 41 | "hex": "#FF0" 42 | } 43 | }, 44 | { 45 | "color": "blue", 46 | "category": "hue", 47 | "type": "primary", 48 | "code": { 49 | "rgba": [ 50 | 0, 51 | 0, 52 | 255, 53 | 1 54 | ], 55 | "hex": "#00F" 56 | } 57 | }, 58 | { 59 | "color": "yellow", 60 | "category": "hue", 61 | "type": "primary", 62 | "code": { 63 | "rgba": [ 64 | 255, 65 | 255, 66 | 0, 67 | 1 68 | ], 69 | "hex": "#FF0" 70 | } 71 | }, 72 | { 73 | "color": "green", 74 | "category": "hue", 75 | "type": "secondary", 76 | "code": { 77 | "rgba": [ 78 | 0, 79 | 255, 80 | 0, 81 | 1 82 | ], 83 | "hex": "#0F0" 84 | } 85 | }, 86 | { 87 | "color": "black", 88 | "category": "hue", 89 | "type": "primary", 90 | "code": { 91 | "rgba": [ 92 | 255, 93 | 255, 94 | 255, 95 | 1 96 | ], 97 | "hex": "#000" 98 | } 99 | }, 100 | { 101 | "color": "white", 102 | "category": "value", 103 | "code": { 104 | "rgba": [ 105 | 0, 106 | 0, 107 | 0, 108 | 1 109 | ], 110 | "hex": "#FFF" 111 | } 112 | }, 113 | { 114 | "color": "red", 115 | "category": "hue", 116 | "type": "primary", 117 | "code": { 118 | "rgba": [ 119 | 255, 120 | 0, 121 | 0, 122 | 1 123 | ], 124 | "hex": "#FF0" 125 | } 126 | }, 127 | { 128 | "color": "blue", 129 | "category": "hue", 130 | "type": "primary", 131 | "code": { 132 | "rgba": [ 133 | 0, 134 | 0, 135 | 255, 136 | 1 137 | ], 138 | "hex": "#00F" 139 | } 140 | }, 141 | { 142 | "color": "yellow", 143 | "category": "hue", 144 | "type": "primary", 145 | "code": { 146 | "rgba": [ 147 | 255, 148 | 255, 149 | 0, 150 | 1 151 | ], 152 | "hex": "#FF0" 153 | } 154 | }, 155 | { 156 | "color": "green", 157 | "category": "hue", 158 | "type": "secondary", 159 | "code": { 160 | "rgba": [ 161 | 0, 162 | 255, 163 | 0, 164 | 1 165 | ], 166 | "hex": "#0F0" 167 | } 168 | }, 169 | { 170 | "color": "black", 171 | "category": "hue", 172 | "type": "primary", 173 | "code": { 174 | "rgba": [ 175 | 255, 176 | 255, 177 | 255, 178 | 1 179 | ], 180 | "hex": "#000" 181 | } 182 | }, 183 | { 184 | "color": "white", 185 | "category": "value", 186 | "code": { 187 | "rgba": [ 188 | 0, 189 | 0, 190 | 0, 191 | 1 192 | ], 193 | "hex": "#FFF" 194 | } 195 | }, 196 | { 197 | "color": "red", 198 | "category": "hue", 199 | "type": "primary", 200 | "code": { 201 | "rgba": [ 202 | 255, 203 | 0, 204 | 0, 205 | 1 206 | ], 207 | "hex": "#FF0" 208 | } 209 | }, 210 | { 211 | "color": "blue", 212 | "category": "hue", 213 | "type": "primary", 214 | "code": { 215 | "rgba": [ 216 | 0, 217 | 0, 218 | 255, 219 | 1 220 | ], 221 | "hex": "#00F" 222 | } 223 | }, 224 | { 225 | "color": "yellow", 226 | "category": "hue", 227 | "type": "primary", 228 | "code": { 229 | "rgba": [ 230 | 255, 231 | 255, 232 | 0, 233 | 1 234 | ], 235 | "hex": "#FF0" 236 | } 237 | }, 238 | { 239 | "color": "green", 240 | "category": "hue", 241 | "type": "secondary", 242 | "code": { 243 | "rgba": [ 244 | 0, 245 | 255, 246 | 0, 247 | 1 248 | ], 249 | "hex": "#0F0" 250 | } 251 | }, 252 | { 253 | "color": "black", 254 | "category": "hue", 255 | "type": "primary", 256 | "code": { 257 | "rgba": [ 258 | 255, 259 | 255, 260 | 255, 261 | 1 262 | ], 263 | "hex": "#000" 264 | } 265 | }, 266 | { 267 | "color": "white", 268 | "category": "value", 269 | "code": { 270 | "rgba": [ 271 | 0, 272 | 0, 273 | 0, 274 | 1 275 | ], 276 | "hex": "#FFF" 277 | } 278 | }, 279 | { 280 | "color": "red", 281 | "category": "hue", 282 | "type": "primary", 283 | "code": { 284 | "rgba": [ 285 | 255, 286 | 0, 287 | 0, 288 | 1 289 | ], 290 | "hex": "#FF0" 291 | } 292 | }, 293 | { 294 | "color": "blue", 295 | "category": "hue", 296 | "type": "primary", 297 | "code": { 298 | "rgba": [ 299 | 0, 300 | 0, 301 | 255, 302 | 1 303 | ], 304 | "hex": "#00F" 305 | } 306 | }, 307 | { 308 | "color": "yellow", 309 | "category": "hue", 310 | "type": "primary", 311 | "code": { 312 | "rgba": [ 313 | 255, 314 | 255, 315 | 0, 316 | 1 317 | ], 318 | "hex": "#FF0" 319 | } 320 | }, 321 | { 322 | "color": "green", 323 | "category": "hue", 324 | "type": "secondary", 325 | "code": { 326 | "rgba": [ 327 | 0, 328 | 255, 329 | 0, 330 | 1 331 | ], 332 | "hex": "#0F0" 333 | } 334 | } 335 | ] 336 | } -------------------------------------------------------------------------------- /chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotchev/rest-bench/44ee0001665ba9012e93b343c5f8c1878fc38dff/chart.png -------------------------------------------------------------------------------- /chart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotchev/rest-bench/44ee0001665ba9012e93b343c5f8c1878fc38dff/chart2.png -------------------------------------------------------------------------------- /colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": "black", 5 | "category": "hue", 6 | "type": "primary", 7 | "code": { 8 | "rgba": [255,255,255,1], 9 | "hex": "#000" 10 | } 11 | }, 12 | { 13 | "color": "white", 14 | "category": "value", 15 | "code": { 16 | "rgba": [0,0,0,1], 17 | "hex": "#FFF" 18 | } 19 | }, 20 | { 21 | "color": "red", 22 | "category": "hue", 23 | "type": "primary", 24 | "code": { 25 | "rgba": [255,0,0,1], 26 | "hex": "#FF0" 27 | } 28 | }, 29 | { 30 | "color": "blue", 31 | "category": "hue", 32 | "type": "primary", 33 | "code": { 34 | "rgba": [0,0,255,1], 35 | "hex": "#00F" 36 | } 37 | }, 38 | { 39 | "color": "yellow", 40 | "category": "hue", 41 | "type": "primary", 42 | "code": { 43 | "rgba": [255,255,0,1], 44 | "hex": "#FF0" 45 | } 46 | }, 47 | { 48 | "color": "green", 49 | "category": "hue", 50 | "type": "secondary", 51 | "code": { 52 | "rgba": [0,255,0,1], 53 | "hex": "#0F0" 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /go.docker: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | RUN mkdir /app 3 | ADD . /app/ 4 | WORKDIR /app 5 | RUN go build -o server . 6 | CMD ["/app/server"] 7 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE") 7 | } 8 | } 9 | 10 | apply plugin: 'java' 11 | apply plugin: 'eclipse' 12 | apply plugin: 'idea' 13 | apply plugin: 'org.springframework.boot' 14 | apply plugin: 'io.spring.dependency-management' 15 | 16 | bootJar { 17 | baseName = 'gs-rest-service' 18 | version = '0.1.0' 19 | } 20 | 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | sourceCompatibility = 1.8 26 | targetCompatibility = 1.8 27 | 28 | dependencies { 29 | compile("org.springframework.boot:spring-boot-starter-web") 30 | testCompile('org.springframework.boot:spring-boot-starter-test') 31 | } 32 | -------------------------------------------------------------------------------- /java/src/main/java/rest/Application.java: -------------------------------------------------------------------------------- 1 | package rest; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } -------------------------------------------------------------------------------- /java/src/main/java/rest/Body.java: -------------------------------------------------------------------------------- 1 | package rest; 2 | 3 | import java.util.List; 4 | 5 | public class Body { 6 | 7 | public static class Code { 8 | public int[] rgba; 9 | public String hex; 10 | } 11 | 12 | public static class Color { 13 | public String color; 14 | public String category; 15 | public String type; 16 | public Code code; 17 | } 18 | 19 | public List colors; 20 | } 21 | -------------------------------------------------------------------------------- /java/src/main/java/rest/Controller.java: -------------------------------------------------------------------------------- 1 | package rest; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class Controller { 9 | 10 | @RequestMapping("/") 11 | public Body post(@RequestBody Body body) { 12 | return body; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /node.docker: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | RUN mkdir /app 3 | ADD . /app/ 4 | WORKDIR /app 5 | CMD ["node","server.js"] 6 | -------------------------------------------------------------------------------- /post.lua: -------------------------------------------------------------------------------- 1 | wrk.method = "POST" 2 | wrk.body = [=[ 3 | { 4 | "colors": [ 5 | { 6 | "color": "black", 7 | "category": "hue", 8 | "type": "primary", 9 | "code": { 10 | "rgba": [255,255,255,1], 11 | "hex": "#000" 12 | } 13 | }, 14 | { 15 | "color": "white", 16 | "category": "value", 17 | "code": { 18 | "rgba": [0,0,0,1], 19 | "hex": "#FFF" 20 | } 21 | }, 22 | { 23 | "color": "red", 24 | "category": "hue", 25 | "type": "primary", 26 | "code": { 27 | "rgba": [255,0,0,1], 28 | "hex": "#FF0" 29 | } 30 | }, 31 | { 32 | "color": "blue", 33 | "category": "hue", 34 | "type": "primary", 35 | "code": { 36 | "rgba": [0,0,255,1], 37 | "hex": "#00F" 38 | } 39 | }, 40 | { 41 | "color": "yellow", 42 | "category": "hue", 43 | "type": "primary", 44 | "code": { 45 | "rgba": [255,255,0,1], 46 | "hex": "#FF0" 47 | } 48 | }, 49 | { 50 | "color": "green", 51 | "category": "hue", 52 | "type": "secondary", 53 | "code": { 54 | "rgba": [0,255,0,1], 55 | "hex": "#0F0" 56 | } 57 | }, 58 | { 59 | "color": "black", 60 | "category": "hue", 61 | "type": "primary", 62 | "code": { 63 | "rgba": [255,255,255,1], 64 | "hex": "#000" 65 | } 66 | }, 67 | { 68 | "color": "white", 69 | "category": "value", 70 | "code": { 71 | "rgba": [0,0,0,1], 72 | "hex": "#FFF" 73 | } 74 | }, 75 | { 76 | "color": "red", 77 | "category": "hue", 78 | "type": "primary", 79 | "code": { 80 | "rgba": [255,0,0,1], 81 | "hex": "#FF0" 82 | } 83 | }, 84 | { 85 | "color": "blue", 86 | "category": "hue", 87 | "type": "primary", 88 | "code": { 89 | "rgba": [0,0,255,1], 90 | "hex": "#00F" 91 | } 92 | }, 93 | { 94 | "color": "yellow", 95 | "category": "hue", 96 | "type": "primary", 97 | "code": { 98 | "rgba": [255,255,0,1], 99 | "hex": "#FF0" 100 | } 101 | }, 102 | { 103 | "color": "green", 104 | "category": "hue", 105 | "type": "secondary", 106 | "code": { 107 | "rgba": [0,255,0,1], 108 | "hex": "#0F0" 109 | } 110 | }, 111 | { 112 | "color": "black", 113 | "category": "hue", 114 | "type": "primary", 115 | "code": { 116 | "rgba": [255,255,255,1], 117 | "hex": "#000" 118 | } 119 | }, 120 | { 121 | "color": "white", 122 | "category": "value", 123 | "code": { 124 | "rgba": [0,0,0,1], 125 | "hex": "#FFF" 126 | } 127 | }, 128 | { 129 | "color": "red", 130 | "category": "hue", 131 | "type": "primary", 132 | "code": { 133 | "rgba": [255,0,0,1], 134 | "hex": "#FF0" 135 | } 136 | }, 137 | { 138 | "color": "blue", 139 | "category": "hue", 140 | "type": "primary", 141 | "code": { 142 | "rgba": [0,0,255,1], 143 | "hex": "#00F" 144 | } 145 | }, 146 | { 147 | "color": "yellow", 148 | "category": "hue", 149 | "type": "primary", 150 | "code": { 151 | "rgba": [255,255,0,1], 152 | "hex": "#FF0" 153 | } 154 | }, 155 | { 156 | "color": "green", 157 | "category": "hue", 158 | "type": "secondary", 159 | "code": { 160 | "rgba": [0,255,0,1], 161 | "hex": "#0F0" 162 | } 163 | }, 164 | { 165 | "color": "black", 166 | "category": "hue", 167 | "type": "primary", 168 | "code": { 169 | "rgba": [255,255,255,1], 170 | "hex": "#000" 171 | } 172 | }, 173 | { 174 | "color": "white", 175 | "category": "value", 176 | "code": { 177 | "rgba": [0,0,0,1], 178 | "hex": "#FFF" 179 | } 180 | }, 181 | { 182 | "color": "red", 183 | "category": "hue", 184 | "type": "primary", 185 | "code": { 186 | "rgba": [255,0,0,1], 187 | "hex": "#FF0" 188 | } 189 | }, 190 | { 191 | "color": "blue", 192 | "category": "hue", 193 | "type": "primary", 194 | "code": { 195 | "rgba": [0,0,255,1], 196 | "hex": "#00F" 197 | } 198 | }, 199 | { 200 | "color": "yellow", 201 | "category": "hue", 202 | "type": "primary", 203 | "code": { 204 | "rgba": [255,255,0,1], 205 | "hex": "#FF0" 206 | } 207 | }, 208 | { 209 | "color": "green", 210 | "category": "hue", 211 | "type": "secondary", 212 | "code": { 213 | "rgba": [0,255,0,1], 214 | "hex": "#0F0" 215 | } 216 | } 217 | ] 218 | } 219 | ]=] 220 | wrk.headers["Content-Type"] = "application/json" 221 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "runtime" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("Version:", runtime.Version()) 12 | fmt.Println("NumCPU:", runtime.NumCPU()) 13 | fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) 14 | 15 | fmt.Println("Listening on port 3000") 16 | err := http.ListenAndServe(":3000", http.HandlerFunc(handler)) 17 | fmt.Println(err) 18 | } 19 | 20 | func handler(w http.ResponseWriter, r *http.Request) { 21 | var body Body 22 | err := json.NewDecoder(r.Body).Decode(&body) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | w.Header().Set("Content-Type", "application/json") 28 | err = json.NewEncoder(w).Encode(body) 29 | if err != nil { 30 | panic(err) 31 | } 32 | } 33 | 34 | type Body struct { 35 | Colors []Color `json:"colors"` 36 | } 37 | 38 | type Color struct { 39 | Color string `json:"color,omitempty"` 40 | Category string `json:"category,omitempty"` 41 | Type string `json:"type,omitempty"` 42 | Code Code `json:"code,omitempty"` 43 | } 44 | 45 | type Code struct { 46 | RGBA []int `json:"rgba,omitempty"` 47 | Hex string `json:"hex,omitempty"` 48 | } 49 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const os = require('os'); 5 | const cluster = require('cluster'); 6 | 7 | const port = process.env.PORT || 3000; 8 | const cpus = os.cpus(); 9 | 10 | if (cluster.isMaster) { 11 | console.log('Node version:', process.version); 12 | console.log('CPU: %d x %s', cpus.length, cpus[0].model); 13 | 14 | if (!process.env.CLUSTER) { 15 | runServer(); 16 | } else { 17 | console.log(`Master ${process.pid} is running`); 18 | 19 | // Fork workers. 20 | for (let i = 0; i < cpus.length; i++) { 21 | cluster.fork(); 22 | } 23 | } 24 | console.log('Listening on port %s', port); 25 | } else { 26 | console.log(`Worker ${process.pid} started`); 27 | 28 | runServer(); 29 | } 30 | 31 | function runServer() { 32 | let server = http.createServer((request, response) => { 33 | const { headers, method, url } = request; 34 | let body = []; 35 | request.on('error', (err) => { 36 | console.error(err); 37 | }).on('data', (chunk) => { 38 | body.push(chunk); 39 | }).on('end', () => { 40 | body = JSON.parse(Buffer.concat(body).toString()); 41 | 42 | response.statusCode = 200; 43 | response.setHeader('Content-Type', 'application/json'); 44 | response.end(JSON.stringify(body)) 45 | }); 46 | }) 47 | 48 | server.listen(port); 49 | } --------------------------------------------------------------------------------