├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── benchmark ├── node-docker │ ├── .dockerignore │ ├── Dockerfile │ ├── package.json │ └── server.js ├── python-cgi │ ├── apache.entrypoint.sh │ ├── handler.py │ ├── Dockerfile │ ├── mpm.conf │ └── httpd.conf ├── ParameterizedSimulation.scala ├── dinosaur_data.csv ├── go-fcgi │ └── fcgiserve.go └── bokeh_notes.py ├── apache.entrypoint.sh ├── .dockerignore ├── nginx.entrypoint.blocking.sh ├── LICENSE.txt ├── nginx.entrypoint.sh ├── src └── main │ └── scala │ ├── CgiUtils.scala │ ├── main.scala │ ├── HTTP.scala │ ├── Dinosaur.scala │ ├── FastCgiUtils.scala │ └── UvUtils.scala ├── nginx.conf ├── Dockerfile.apache2 ├── Dockerfile ├── Dockerfile.nginx ├── Dockerfile.goproxy ├── Dockerfile.goproxy.blocking ├── README.md ├── mpm.conf └── httpd.conf /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.15 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | project/project 2 | project/target 3 | .git 4 | target 5 | -------------------------------------------------------------------------------- /benchmark/node-docker/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.3") 2 | -------------------------------------------------------------------------------- /apache.entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | apachectl start 3 | while true; do sleep 10000; done 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | project/project/target 2 | project/target 3 | target/ 4 | Dockerfile 5 | .dockerignore 6 | examples -------------------------------------------------------------------------------- /benchmark/python-cgi/apache.entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | apachectl start 3 | while true; do sleep 10000; done 4 | -------------------------------------------------------------------------------- /benchmark/node-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:boron 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . /usr/src/app 13 | 14 | EXPOSE 8080 15 | CMD [ "npm", "start" ] 16 | -------------------------------------------------------------------------------- /benchmark/node-docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker_web_app", 3 | "version": "1.0.0", 4 | "description": "Node.js on Docker", 5 | "author": "First Last ", 6 | "main": "server.js", 7 | "scripts": { 8 | "start": "node server.js" 9 | }, 10 | "dependencies": { 11 | "express": "^4.13.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /benchmark/node-docker/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | // Constants 6 | const PORT = 8080; 7 | const HOST = '0.0.0.0'; 8 | 9 | // App 10 | const app = express(); 11 | app.get('/', (req, res) => { 12 | res.send('Hello world\n'); 13 | }); 14 | 15 | app.listen(PORT, HOST); 16 | console.log(`Running on http://${HOST}:${PORT}`); 17 | -------------------------------------------------------------------------------- /benchmark/python-cgi/handler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os, cgi, sys 3 | 4 | if __name__ == "__main__": 5 | fields = cgi.FieldStorage() 6 | path_info = os.environ.get("PATH_INFO","") 7 | path_components = path_info.split("/") 8 | sys.stdout.write("Content-type: text/html\r\n\r\n") 9 | print("hello\n") 10 | print(fields) 11 | print(path_components) -------------------------------------------------------------------------------- /nginx.entrypoint.blocking.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm /tmp/app.socket 3 | rm /tmp/app.fifo 4 | mkfifo /tmp/app.fifo 5 | nginx -g "daemon off;" & 6 | export ROUTER_MODE=FCGI 7 | # nc -l -U /tmp/app.socket < /tmp/app.fifo | /var/www/localhost/cgi-bin/dinosaur-build-out > /tmp/app.fifo 8 | socat UNIX-LISTEN:/tmp/app.socket,fork,max-children=48,backlog=4096 EXEC:/var/www/localhost/cgi-bin/dinosaur-build-out 9 | -------------------------------------------------------------------------------- /benchmark/python-cgi/Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from a clean Alpine image 2 | FROM alpine:3.3 3 | RUN apk --update add apache2 apache2-utils python3 4 | ADD handler.py /var/www/localhost/cgi-bin/handler.py 5 | 6 | COPY httpd.conf /etc/apache2/httpd.conf 7 | COPY mpm.conf /etc/apache2/mpm.conf 8 | 9 | 10 | RUN mkdir -p /run/apache2 11 | ADD apache.entrypoint.sh /root/ 12 | 13 | ENTRYPOINT "/root/apache.entrypoint.sh" 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /nginx.entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm /tmp/app.socket 3 | rm /tmp/app.fifo 4 | mkfifo /tmp/app.fifo 5 | nginx -g "daemon off;" & 6 | export ROUTER_MODE=UVFCGI 7 | # nc -l -U /tmp/app.socket < /tmp/app.fifo | /var/www/localhost/cgi-bin/dinosaur-build-out > /tmp/app.fifo 8 | # socat UNIX-LISTEN:/tmp/app.socket,fork,max-children=48,backlog=4096 EXEC:/var/www/localhost/cgi-bin/dinosaur-build-out 9 | while true; do 10 | /var/www/localhost/cgi-bin/dinosaur-build-out 11 | echo "restarting server" 12 | rm /tmp/app.socket 13 | done 14 | -------------------------------------------------------------------------------- /benchmark/ParameterizedSimulation.scala: -------------------------------------------------------------------------------- 1 | import io.gatling.core.Predef._ 2 | import io.gatling.http.Predef._ 3 | import scala.concurrent.duration._ 4 | 5 | class ParameterizedSimulation extends Simulation { 6 | val url = System.getenv("GATLING_URL") 7 | val requests = Integer.parseInt(System.getenv("GATLING_REQUESTS")) 8 | val users = Integer.parseInt(System.getenv("GATLING_USERS")) 9 | val reqs_per_user = requests / users 10 | val rampTime = Integer.parseInt(System.getenv("GATLING_RAMP_TIME")) 11 | val scn = scenario("My scenario").repeat(reqs_per_user) { 12 | exec( 13 | http("Dinosaur") 14 | .get(url) 15 | .check(status.in(Seq(200,304))) 16 | ) 17 | } 18 | 19 | setUp(scn.inject(rampUsers(users) over (rampTime seconds))) 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/CgiUtils.scala: -------------------------------------------------------------------------------- 1 | package io.dinosaur 2 | import scalanative.native._ 3 | 4 | object CgiUtils { 5 | def env(key: CString): String = { 6 | val lookup = stdlib.getenv(key) 7 | if (lookup == null) { 8 | "" 9 | } else { 10 | fromCString(lookup) 11 | } 12 | } 13 | 14 | def parsePathInfo(pathInfo: String): Seq[String] = { 15 | pathInfo.split("/").filter( _ != "" ) 16 | } 17 | 18 | def parseQueryString(queryString: String): Function1[String, Seq[String]] = { 19 | val pairs = queryString.split("&").map( pair => 20 | pair.split("=") match { 21 | case Array(key, value) => (key,value) 22 | } 23 | ).groupBy(_._1).toSeq 24 | val groupedValues = for ( (k,v) <- pairs; 25 | values = v.toSeq.map(_._2) ) 26 | yield (k -> values) 27 | return groupedValues.toMap.getOrElse(_,Seq.empty) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/main.scala: -------------------------------------------------------------------------------- 1 | package io.dinosaur.main 2 | import io.dinosaur._ 3 | import scalanative.native._ 4 | import scala.concurrent.Future 5 | import scala.concurrent.ExecutionContext.Implicits.global 6 | 7 | object main { 8 | def main(args: Array[String]): Unit = { 9 | Router.init() 10 | .get("/")("

Welcome to Dinosaur!

") 11 | .get("/hello") { request => 12 | "Hello World!" 13 | } 14 | .get("/who")( request => 15 | request.pathInfo() match { 16 | case Seq("who") => "Who's there?" 17 | case Seq("who",x) => "Hello, " + x 18 | case Seq("who",x,y) => "Hello both of you" 19 | case _ => "Hello y'all!" 20 | } 21 | ) 22 | .get("/bye")( request => 23 | request.params("who") 24 | .map { x => "Bye, " + x } 25 | .mkString(". ") 26 | ) 27 | .dispatch() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/HTTP.scala: -------------------------------------------------------------------------------- 1 | package io.dinosaur 2 | import scala.language.implicitConversions 3 | import scalanative.native._ 4 | 5 | package object dinosaur {} 6 | 7 | sealed trait Method 8 | case object GET extends Method 9 | case object POST extends Method 10 | case object PUT extends Method 11 | case object DELETE extends Method 12 | case object HEAD extends Method 13 | case object OPTIONS extends Method 14 | case object CONNECT extends Method 15 | case object PATCH extends Method 16 | 17 | case class Request( 18 | method: Function0[Method], 19 | pathInfo: Function0[Seq[String]], 20 | params: Function1[String, Seq[String]] 21 | ) 22 | 23 | case class Response( 24 | body: ResponseBody, 25 | statusCode: Int = 200, 26 | headers: Map[String, String] = Map("Content-type" -> "text/html; charset=utf-8") 27 | ) { 28 | def bodyToString() = this.body match { 29 | case StringBody(body) => body 30 | } 31 | def inferHeaders(): Map[String, String] = { 32 | if (this.headers.contains("Content-type")) return this.headers 33 | else { 34 | val inferredContentType = this.body match { 35 | case StringBody(_) => "text/html; charset=utf-8" 36 | } 37 | val inferredHeaders = this.headers + ("Content-type" -> inferredContentType) 38 | return inferredHeaders 39 | } 40 | } 41 | } 42 | 43 | sealed trait ResponseBody 44 | case class StringBody(body:String) extends ResponseBody 45 | // TODO: HTML and JSON. Protobuf, Avro, Thrift? 46 | 47 | object Response { 48 | implicit def stringToResponse(body:String):Response = Response(StringBody(body)) 49 | implicit def stringToRequestResponse(body:String):(Request => Response) = _ => Response(StringBody(body)) 50 | } 51 | -------------------------------------------------------------------------------- /benchmark/dinosaur_data.csv: -------------------------------------------------------------------------------- 1 | Server,Requests,Users,RampTime,,error rate,min (ms),max (ms),mean (ms),50th (ms),99th (ms),req/sec 2 | Dinosaur-CGI,500,10,0,,0,6,266,42,35,171,125 3 | Dinosaur-CGI,1250,25,2,,0,5,195,73,71,161,208.33 4 | Dinosaur-CGI,2500,50,3,,0,5,715,166,164,421,208.33 5 | Dinosaur-CGI,3750,75,5,,0,7,1143,229,222,641,234.375 6 | Dinosaur-CGI,5000,100,5,,0,4,3463,250,228,701,250 7 | Dinosaur-CGI,7500,150,7,,0,5,3942,365,324,2724,277.778 8 | Dinosaur-CGI,10000,200,10,,0.0001,0,5020,472,411,4120,303.03 9 | Dinosaur-CGI,12500,250,10,,0,4,4453,529,506,2777,320.513 10 | Dinosaur-CGI,15000,300,10,,0.0044,0,4645,625,644,1746,219.149 11 | Dinosaur-CGI,17500,350,10,,0.1404,0,9992,584,528,2592,318.182 12 | Dinosaur-CGI,20000,400,10,,0.293,0,8386,538,545,2661,384.615 13 | Dinosaur-CGI,22500,450,10,,0.2928,0,9990,609,576,2997,335.821 14 | Dinosaur-CGI,25000,500,10,,0.334,0,9999,538,424,4735,403.226 15 | Dinosaur-CGI,37500,750,10,,0.3423,0,10008,608,536,5137,407.609 16 | Dinosaur-CGI,50000,1000,10,,0.4449,0,9995,591,464,3861,427.35 17 | Dinosaur-CGI,75000,1500,10,,0.4412,0,10009,572,555,4485,474.684 18 | Dinosaur-CGI,100000,2000,10,,0.5238,0,9993,593,559,5484,458.716 19 | Node-Express,500,10,0,,0,1,115,10,6,45,250 20 | Node-Express,1250,25,0,,0,1,223,24,19,163,625 21 | Node-Express,2500,50,0,,0,1,186,43,39,160,833.33 22 | Node-Express,3750,75,0,,0,1,231,56,51,193,937.5 23 | Node-Express,5000,100,0,,0,1,278,74,68,197,1000 24 | Node-Express,7500,150,0,,0,1,554,109,95,368,1071.429 25 | Node-Express,10000,200,0,,0,1,468,130,125,338,1250 26 | Node-Express,12500,250,0,,0,1,576,150,148,354,1388.889 27 | Node-Express,15000,300,0,,0,1,888,176,167,477,1363.636 28 | Node-Express,17500,350,0,,0,0,655,197,182,496,1458.333 29 | Node-Express,20000,400,0,,0,1,1759,237,224,611,1428.571 30 | Node-Express,22500,450,0,,0.013,0,1893,260,251,809,1500 31 | Node-Express,25000,500,5,,0.22,0,2798,203,118,845,625 32 | Node-Express,37500,750,5,,0.161,0,9328,156,94,916,937.5 33 | Node-Express,50000,1000,5,,0.134,0,9240,187,134,974,1190.476 34 | Node-Express,75000,1500,5,,0.15,0,10009,139,351,975,1595.645 35 | Node-Express,100000,2000,5,,0.151,0,9665,166,113,851,1754.386 36 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | error_log /var/log/nginx/error.log debug; 2 | pid /var/run/nginx.pid; 3 | user root; 4 | worker_processes 1; 5 | events { 6 | worker_connections 1024; 7 | } 8 | http { 9 | include /etc/nginx/mime.types; 10 | client_body_buffer_size 10K; 11 | client_header_buffer_size 1k; 12 | large_client_header_buffers 2 1k; 13 | client_max_body_size 8m; 14 | default_type application/octet-stream; 15 | keepalive_timeout 65; 16 | log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' 17 | '\$status \$body_bytes_sent "\$http_referer" ' 18 | '"\$http_user_agent" "\$http_x_forwarded_for"'; 19 | access_log /var/log/nginx/access.log main; 20 | upstream dinosaur { 21 | server unix:/tmp/app.socket; 22 | keepalive 1; 23 | } 24 | server { 25 | root /var/www; 26 | listen 80 default_server backlog=4096; 27 | charset utf-8; 28 | index index.html; 29 | log_not_found on; 30 | location = /favicon.ico { 31 | access_log off; 32 | } 33 | location ~ /app/ { 34 | 35 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 36 | fastcgi_param SERVER_SOFTWARE nginx; 37 | fastcgi_param QUERY_STRING $query_string; 38 | fastcgi_param REQUEST_METHOD $request_method; 39 | fastcgi_param CONTENT_TYPE $content_type; 40 | fastcgi_param CONTENT_LENGTH $content_length; 41 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 42 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 43 | fastcgi_param REQUEST_URI $request_uri; 44 | fastcgi_param DOCUMENT_URI $document_uri; 45 | fastcgi_param DOCUMENT_ROOT $document_root; 46 | fastcgi_param SERVER_PROTOCOL $server_protocol; 47 | fastcgi_param REMOTE_ADDR $remote_addr; 48 | fastcgi_param REMOTE_PORT $remote_port; 49 | fastcgi_param SERVER_ADDR $server_addr; 50 | fastcgi_param SERVER_PORT $server_port; 51 | fastcgi_param SERVER_NAME $server_name; 52 | fastcgi_buffering on; 53 | fastcgi_keep_conn on; 54 | fastcgi_pass dinosaur; 55 | fastcgi_read_timeout 10s; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Dockerfile.apache2: -------------------------------------------------------------------------------- 1 | # Start from Alpine and Java 8, and name this stage "build" 2 | FROM openjdk:8u121-jre-alpine AS build 3 | # Install C libraries and build tools 4 | RUN echo "installing dependencies" && \ 5 | apk --update add gc-dev clang musl-dev libc-dev build-base git && \ 6 | apk add libunwind-dev --update-cache --repository http://nl.alpinelinux.org/alpine/edge/main 7 | # Install re2 from source for clang++ compatability 8 | RUN git clone https://github.com/google/re2.git && cd re2 && \ 9 | CXX=clang++ make && make install 10 | 11 | # Install SBT 12 | ENV SBT_VERSION 0.13.15 13 | ENV SBT_HOME /usr/local/sbt 14 | ENV PATH ${PATH}:${SBT_HOME}/bin 15 | RUN echo "installing SBT $SBT_VERSION" && \ 16 | apk add --no-cache --update bash wget && mkdir -p "$SBT_HOME" && \ 17 | wget -qO - --no-check-certificate "https://dl.bintray.com/sbt/native-packages/sbt/$SBT_VERSION/sbt-$SBT_VERSION.tgz" | tar xz -C $SBT_HOME --strip-components=1 && \ 18 | echo -ne "- with sbt $SBT_VERSION\n" >> /root/.built && \ 19 | sbt sbtVersion 20 | 21 | # Set up the directory structure for our project 22 | RUN mkdir -p /root/project-build/project 23 | WORKDIR /root/project-build 24 | 25 | # Resolve all our dependencies and plugins to speed up future compilations 26 | ADD ./project/plugins.sbt project/ 27 | ADD ./project/build.properties project/ 28 | ADD build.sbt . 29 | RUN sbt update 30 | 31 | # Add and compile our actual application source code 32 | ADD . /root/project-build/ 33 | RUN sbt clean nativeLink 34 | 35 | # Copy the binary executable to a consistent location 36 | RUN cp ./target/scala-2.11/*-out ./dinosaur-build-out 37 | 38 | # Start over from a clean Alpine image 39 | FROM alpine:3.3 40 | 41 | # Copy in C libraries 42 | COPY --from=build \ 43 | /usr/lib/libunwind.so.8 \ 44 | /usr/lib/libunwind-x86_64.so.8 \ 45 | /usr/lib/libgc.so.1 \ 46 | /usr/lib/libstdc++.so.6 \ 47 | /usr/lib/libgcc_s.so.1 \ 48 | /usr/lib/ 49 | COPY --from=build \ 50 | /usr/local/lib/libre2.so.0 \ 51 | /usr/local/lib/libre2.so.0 52 | 53 | # Copy in the executable 54 | COPY --from=build \ 55 | /root/project-build/dinosaur-build-out /var/www/localhost/cgi-bin/app 56 | 57 | COPY httpd.conf /etc/apache2/httpd.conf 58 | COPY mpm.conf /etc/apache2/mpm.conf 59 | 60 | RUN apk --update add apache2 apache2-utils 61 | 62 | RUN mkdir -p /run/apache2 63 | ADD apache.entrypoint.sh /root/ 64 | 65 | ENTRYPOINT "/root/apache.entrypoint.sh" 66 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from Alpine and Java 8, and name this stage "build" 2 | FROM openjdk:8u121-jre-alpine AS build 3 | # Install C libraries and build tools 4 | RUN echo "installing dependencies" && \ 5 | apk --update add gc-dev clang musl-dev libc-dev build-base git && \ 6 | apk add libunwind-dev --update-cache --repository http://nl.alpinelinux.org/alpine/edge/main 7 | # Install re2 from source for clang++ compatability 8 | RUN git clone https://github.com/google/re2.git && cd re2 && \ 9 | CXX=clang++ make && make install 10 | 11 | # Install SBT 12 | ENV SBT_VERSION 0.13.15 13 | ENV SBT_HOME /usr/local/sbt 14 | ENV PATH ${PATH}:${SBT_HOME}/bin 15 | RUN echo "installing SBT $SBT_VERSION" && \ 16 | apk add --no-cache --update bash wget && mkdir -p "$SBT_HOME" && \ 17 | wget -qO - --no-check-certificate "https://dl.bintray.com/sbt/native-packages/sbt/$SBT_VERSION/sbt-$SBT_VERSION.tgz" | tar xz -C $SBT_HOME --strip-components=1 && \ 18 | echo -ne "- with sbt $SBT_VERSION\n" >> /root/.built && \ 19 | sbt sbtVersion 20 | 21 | # Set up the directory structure for our project 22 | RUN mkdir -p /root/project-build/project 23 | WORKDIR /root/project-build 24 | 25 | # Resolve all our dependencies and plugins to speed up future compilations 26 | ADD ./project/plugins.sbt project/ 27 | ADD ./project/build.properties project/ 28 | ADD build.sbt project/ src/ ./ 29 | RUN sbt update 30 | 31 | # Add and compile our actual application source code 32 | ADD . /root/project-build/ 33 | RUN sbt clean nativeLink 34 | 35 | # Copy the binary executable to a consistent location 36 | RUN cp ./target/scala-2.11/*-out ./dinosaur-build-out 37 | 38 | # Start over from a clean Alpine image 39 | FROM alpine:3.3 40 | 41 | # Copy in C libraries 42 | COPY --from=build \ 43 | /usr/lib/libunwind.so.8 \ 44 | /usr/lib/libunwind-x86_64.so.8 \ 45 | /usr/lib/libgc.so.1 \ 46 | /usr/lib/libstdc++.so.6 \ 47 | /usr/lib/libgcc_s.so.1 \ 48 | /usr/lib/ 49 | COPY --from=build \ 50 | /usr/local/lib/libre2.so.0 \ 51 | /usr/local/lib/libre2.so.0 52 | 53 | # Copy in the executable 54 | COPY --from=build \ 55 | /root/project-build/dinosaur-build-out /var/www/localhost/cgi-bin/app 56 | 57 | COPY httpd.conf /etc/apache2/httpd.conf 58 | COPY mpm.conf /etc/apache2/mpm.conf 59 | 60 | RUN apk --update add apache2 apache2-utils 61 | 62 | RUN mkdir -p /run/apache2 63 | ADD apache.entrypoint.sh /root/ 64 | 65 | ENTRYPOINT "/root/apache.entrypoint.sh" 66 | -------------------------------------------------------------------------------- /Dockerfile.nginx: -------------------------------------------------------------------------------- 1 | # Start from Alpine and Java 8, and name this stage "build" 2 | FROM openjdk:8u121-jre-alpine AS build 3 | # Install C libraries and build tools 4 | RUN echo "installing dependencies" && \ 5 | apk --update add gc-dev clang musl-dev libc-dev build-base git && \ 6 | apk add libunwind-dev --update-cache --repository http://nl.alpinelinux.org/alpine/edge/main && \ 7 | apk add libuv-dev 8 | # Install re2 from source for clang++ compatability 9 | RUN git clone https://github.com/google/re2.git && cd re2 && \ 10 | CXX=clang++ make && make install 11 | 12 | # Install SBT 13 | ENV SBT_VERSION 0.13.15 14 | ENV SBT_HOME /usr/local/sbt 15 | ENV PATH ${PATH}:${SBT_HOME}/bin 16 | RUN echo "installing SBT $SBT_VERSION" && \ 17 | apk add --no-cache --update bash wget && mkdir -p "$SBT_HOME" && \ 18 | wget -qO - --no-check-certificate "https://dl.bintray.com/sbt/native-packages/sbt/$SBT_VERSION/sbt-$SBT_VERSION.tgz" | tar xz -C $SBT_HOME --strip-components=1 && \ 19 | echo -ne "- with sbt $SBT_VERSION\n" >> /root/.built && \ 20 | sbt sbtVersion 21 | 22 | # Set up the directory structure for our project 23 | RUN mkdir -p /root/project-build/project 24 | WORKDIR /root/project-build 25 | 26 | # Resolve all our dependencies and plugins to speed up future compilations 27 | ADD ./project/plugins.sbt project/ 28 | ADD ./project/build.properties project/ 29 | ADD build.sbt . 30 | RUN sbt update 31 | 32 | # Add and compile our actual application source code 33 | ADD ./src src/ 34 | RUN sbt clean nativeLink 35 | 36 | # Copy the binary executable to a consistent location 37 | RUN cp ./target/scala-2.11/*-out ./dinosaur-build-out 38 | 39 | # Start over from a clean Alpine image 40 | FROM alpine:3.3 41 | 42 | # Copy in C libraries 43 | COPY --from=build \ 44 | /usr/lib/libunwind.so.8 \ 45 | /usr/lib/libunwind-x86_64.so.8 \ 46 | /usr/lib/libgc.so.1 \ 47 | /usr/lib/libstdc++.so.6 \ 48 | /usr/lib/libgcc_s.so.1 \ 49 | /usr/lib/libuv.so.1 \ 50 | /usr/lib/ 51 | COPY --from=build \ 52 | /usr/local/lib/libre2.so.0 \ 53 | /usr/local/lib/libre2.so.0 54 | 55 | # Copy in the executable 56 | COPY --from=build \ 57 | /root/project-build/dinosaur-build-out /var/www/localhost/cgi-bin/dinosaur-build-out 58 | 59 | COPY httpd.conf /etc/apache2/httpd.conf 60 | COPY mpm.conf /etc/apache2/mpm.conf 61 | 62 | RUN apk --update add nginx socat bash 63 | COPY nginx.conf /etc/nginx/nginx.conf 64 | 65 | ADD nginx.entrypoint.sh / 66 | 67 | ENTRYPOINT ["./nginx.entrypoint.sh"] 68 | -------------------------------------------------------------------------------- /benchmark/go-fcgi/fcgiserve.go: -------------------------------------------------------------------------------- 1 | package main 2 | import "github.com/tomasen/fcgi_client" 3 | import "log" 4 | import "io/ioutil" 5 | import "os" 6 | import "net/http" 7 | 8 | type reqResp struct { 9 | r *http.Request 10 | respChan chan codeContent 11 | } 12 | 13 | type codeContent struct { 14 | content []byte 15 | code int 16 | } 17 | 18 | func handleReq(fcgi *fcgiclient.FCGIClient, r *http.Request, respChan chan codeContent) { 19 | defer func() { 20 | if err := recover(); err != nil { 21 | log.Println("ERROR IN DOWNSTREAM COMM", err) 22 | respChan <- codeContent{[]byte("ERROR"),500} 23 | panic(err) 24 | } 25 | }() 26 | // log.Println("received request from", r.URL.Path) 27 | 28 | env := make(map[string]string) 29 | env["SCRIPT_FILENAME"] = "/home/www/test.php" 30 | env["SERVER_SOFTWARE"] = "go / fcgiclient " 31 | env["REMOTE_ADDR"] = "127.0.0.1" 32 | env["QUERY_STRING"] = "" 33 | env["PATH_INFO"] = r.URL.Path 34 | 35 | // log.Println("sending binary request downstream") 36 | resp, err := fcgi.Get(env) 37 | if err != nil { 38 | log.Println("err:", err) 39 | } 40 | 41 | // log.Println("awaiting binary response from downstream") 42 | content, err := ioutil.ReadAll(resp.Body) 43 | if err != nil { 44 | log.Println("err:", err) 45 | } 46 | // log.Println("got binary response from downstream", content) 47 | respChan <- codeContent{content,200} 48 | // log.Println("done") 49 | } 50 | 51 | func fcgiWorker(requests chan reqResp) { 52 | // log.Println("Connecting to ", os.Args[2]) 53 | fcgi, err := fcgiclient.Dial("unix", os.Args[2]) 54 | if err != nil { 55 | log.Println("err:", err) 56 | } 57 | 58 | defer func() { 59 | err := recover() 60 | log.Println("Got an error in worker loop, trying to recover", err) 61 | fcgiWorker(requests) 62 | }() 63 | 64 | for { 65 | reqResp := <-requests 66 | handleReq(fcgi, reqResp.r, reqResp.respChan) 67 | } 68 | } 69 | 70 | func main() { 71 | requests := make (chan reqResp) 72 | for i := 0; i < 1; i++ { 73 | go fcgiWorker(requests) 74 | } 75 | handler := func (w http.ResponseWriter, r *http.Request) { 76 | // log.Println("sending request to worker") 77 | respChan := make(chan codeContent) 78 | requests <- reqResp{r, respChan} 79 | // log.Println("awaiting response from worker") 80 | resp := <-respChan 81 | // log.Println("writing response upstream") 82 | w.WriteHeader(resp.code) 83 | w.Write(resp.content) 84 | // log.Println("done.") 85 | } 86 | http.HandleFunc("/", handler) 87 | http.ListenAndServe(os.Args[1], nil) 88 | } 89 | -------------------------------------------------------------------------------- /benchmark/bokeh_notes.py: -------------------------------------------------------------------------------- 1 | from bokeh.plotting import figure, output_notebook, show 2 | import pandas as pd 3 | output_notebook() 4 | df = pd.read_csv("data.csv") 5 | 6 | cats = [str(r) for r in df["Users"].unique()] 7 | 8 | colors = ["dodgerblue", "limegreen", "orange"] 9 | 10 | dino = df.query('Server == "Dinosaur-CGI"') 11 | node = df.query('Server == "Node-Express"') 12 | pyth = df.query('Server == "Python-CGI"') 13 | 14 | p1 = figure(tools="save", title="Dinosaur-CGI response times under load (median, 99th percentile, max)", x_range=cats, x_axis_label="Connections", y_axis_label="Response time (ms)", width=650, height=300, y_axis_type="log", y_range=(10,12000)) 15 | 16 | p1.circle(cats, dino["50th (ms)"], size=15, fill_alpha=0, line_width=2, color=colors[0]) 17 | p1.circle(cats, dino["99th (ms)"], size=8, color=colors[0]) 18 | p1.rect(cats, dino["max (ms)"], 0.4, 0.1, color=colors[0]) 19 | 20 | p1.segment(cats, dino["50th (ms)"], cats, dino["99th (ms)"], line_width=3, color=colors[0]) 21 | p1.segment(cats, dino["99th (ms)"], cats, dino["max (ms)"], line_width=2, color=colors[0]) 22 | 23 | dino_error_rate_size = (15 * dino["error rate"]) 24 | # p1.circle(cats, dino["mean (ms)"], size=dino_error_rate_size, color="red") 25 | 26 | p2 = figure(tools="save", title="NodeJS response times under load (median, 99th percentile, max)", x_range=cats, x_axis_label="Connections", y_axis_label="Response time (ms)", width=650, height=300, y_axis_type="log", y_range=(10,12000)) 27 | 28 | p2.circle(cats, node["50th (ms)"], size=15, fill_alpha=0, line_width=2, color=colors[1]) 29 | p2.circle(cats, node["99th (ms)"],size=8, color=colors[1]) 30 | p2.rect(cats, node["max (ms)"], 0.4, 0.1, color=colors[1]) 31 | 32 | p2.segment(cats, node["50th (ms)"], cats, node["99th (ms)"], line_width=3, color=colors[1]) 33 | p2.segment(cats, node["99th (ms)"], cats, node["max (ms)"], line_width=2, color=colors[1]) 34 | 35 | node_error_rate_size = (15 * node["error rate"]) 36 | # p2.circle(cats, node["mean (ms)"], size=node_error_rate_size, color="red") 37 | 38 | p3 = figure(tools="save", title="Python-CGI response times under load (median, 99th percentile, max)", x_range=cats, x_axis_label="Connections", y_axis_label="Response time (ms)", width=650, height=300, y_axis_type="log", y_range=(10,12000)) 39 | 40 | p3.circle(cats, pyth["50th (ms)"], size=15, fill_alpha=0, line_width=2, color=colors[2]) 41 | p3.circle(cats, pyth["99th (ms)"],size=8, color=colors[2]) 42 | p3.rect(cats, pyth["max (ms)"], 0.4, 0.1, color=colors[2]) 43 | 44 | p3.segment(cats, pyth["50th (ms)"], cats, pyth["99th (ms)"], line_width=3, color=colors[2]) 45 | p3.segment(cats, pyth["99th (ms)"], cats, pyth["max (ms)"], line_width=2, color=colors[2]) 46 | 47 | show(p1) 48 | show(p2) 49 | show(p3) -------------------------------------------------------------------------------- /Dockerfile.goproxy: -------------------------------------------------------------------------------- 1 | # Start from Alpine and Java 8, and name this stage "build" 2 | FROM openjdk:8u121-jre-alpine AS build 3 | # Install C libraries and build tools 4 | RUN echo "installing dependencies" && \ 5 | apk --update add gc-dev clang musl-dev libc-dev build-base git && \ 6 | apk add libunwind-dev --update-cache --repository http://nl.alpinelinux.org/alpine/edge/main && \ 7 | apk add libuv-dev 8 | # Install re2 from source for clang++ compatability 9 | RUN git clone https://github.com/google/re2.git && cd re2 && \ 10 | CXX=clang++ make && make install 11 | 12 | # Install SBT 13 | ENV SBT_VERSION 0.13.15 14 | ENV SBT_HOME /usr/local/sbt 15 | ENV PATH ${PATH}:${SBT_HOME}/bin 16 | RUN echo "installing SBT $SBT_VERSION" && \ 17 | apk add --no-cache --update bash wget && mkdir -p "$SBT_HOME" && \ 18 | wget -qO - --no-check-certificate "https://dl.bintray.com/sbt/native-packages/sbt/$SBT_VERSION/sbt-$SBT_VERSION.tgz" | tar xz -C $SBT_HOME --strip-components=1 && \ 19 | echo -ne "- with sbt $SBT_VERSION\n" >> /root/.built && \ 20 | sbt sbtVersion 21 | 22 | # Set up the directory structure for our project 23 | RUN mkdir -p /root/project-build/project 24 | WORKDIR /root/project-build 25 | 26 | # Resolve all our dependencies and plugins to speed up future compilations 27 | ADD ./project/plugins.sbt project/ 28 | ADD ./project/build.properties project/ 29 | ADD build.sbt . 30 | RUN sbt update 31 | 32 | # Add and compile our actual application source code 33 | ADD ./src src/ 34 | RUN sbt clean nativeLink 35 | 36 | # Copy the binary executable to a consistent location 37 | RUN cp ./target/scala-2.11/*-out ./dinosaur-build-out 38 | 39 | # Start over from scratch 40 | FROM alpine:3.3 41 | 42 | # Copy in C libraries 43 | COPY --from=build \ 44 | /usr/lib/libunwind.so.8 \ 45 | /usr/lib/libunwind-x86_64.so.8 \ 46 | /usr/lib/libgc.so.1 \ 47 | /usr/lib/libstdc++.so.6 \ 48 | /usr/lib/libgcc_s.so.1 \ 49 | /usr/lib/libuv.so.1 \ 50 | /usr/lib/ 51 | COPY --from=build \ 52 | /usr/local/lib/libre2.so.0 \ 53 | /usr/local/lib/libre2.so.0 54 | 55 | # Copy in the executable 56 | COPY --from=build \ 57 | /root/project-build/dinosaur-build-out /dinosaur-build-out 58 | 59 | RUN apk --update add go socat netcat-openbsd bash git vim 60 | ENV GOROOT /usr/lib/go 61 | ENV GOPATH /go 62 | ENV PATH /go/bin:$PATH 63 | RUN ["go", "get", "github.com/tomasen/fcgi_client"] 64 | 65 | ADD ./benchmark/go-fcgi /go/fcgi 66 | WORKDIR /go/fcgi 67 | 68 | RUN ["go", "build"] 69 | 70 | 71 | ENTRYPOINT ["bash", "-c"] 72 | # ENV ROUTER_MODE FCGI 73 | # CMD ["socat UNIX-LISTEN:/tmp/app.socket,fork,max-children=4,backlog=4096 EXEC:/dinosaur-build-out > fcgi.log 2> fcgi.error & ./fcgi 0.0.0.0:8080 /tmp/app.socket & tail -f fcgi.log"] 74 | ENV ROUTER_MODE UVFCGI 75 | CMD ["/dinosaur-build-out > fcgi.log 2> fcgi.error & sleep 1 && ./fcgi 0.0.0.0:8080 /tmp/app.socket & tail -f fcgi.log"] 76 | -------------------------------------------------------------------------------- /Dockerfile.goproxy.blocking: -------------------------------------------------------------------------------- 1 | # Start from Alpine and Java 8, and name this stage "build" 2 | FROM openjdk:8u121-jre-alpine AS build 3 | # Install C libraries and build tools 4 | RUN echo "installing dependencies" && \ 5 | apk --update add gc-dev clang musl-dev libc-dev build-base git && \ 6 | apk add libunwind-dev --update-cache --repository http://nl.alpinelinux.org/alpine/edge/main && \ 7 | apk add libuv-dev 8 | # Install re2 from source for clang++ compatability 9 | RUN git clone https://github.com/google/re2.git && cd re2 && \ 10 | CXX=clang++ make && make install 11 | 12 | # Install SBT 13 | ENV SBT_VERSION 0.13.15 14 | ENV SBT_HOME /usr/local/sbt 15 | ENV PATH ${PATH}:${SBT_HOME}/bin 16 | RUN echo "installing SBT $SBT_VERSION" && \ 17 | apk add --no-cache --update bash wget && mkdir -p "$SBT_HOME" && \ 18 | wget -qO - --no-check-certificate "https://dl.bintray.com/sbt/native-packages/sbt/$SBT_VERSION/sbt-$SBT_VERSION.tgz" | tar xz -C $SBT_HOME --strip-components=1 && \ 19 | echo -ne "- with sbt $SBT_VERSION\n" >> /root/.built && \ 20 | sbt sbtVersion 21 | 22 | # Set up the directory structure for our project 23 | RUN mkdir -p /root/project-build/project 24 | WORKDIR /root/project-build 25 | 26 | # Resolve all our dependencies and plugins to speed up future compilations 27 | ADD ./project/plugins.sbt project/ 28 | ADD ./project/build.properties project/ 29 | ADD build.sbt . 30 | RUN sbt update 31 | 32 | # Add and compile our actual application source code 33 | ADD ./src src/ 34 | RUN sbt clean nativeLink 35 | 36 | # Copy the binary executable to a consistent location 37 | RUN cp ./target/scala-2.11/*-out ./dinosaur-build-out 38 | 39 | # Start over from scratch 40 | FROM alpine:3.3 41 | 42 | # Copy in C libraries 43 | COPY --from=build \ 44 | /usr/lib/libunwind.so.8 \ 45 | /usr/lib/libunwind-x86_64.so.8 \ 46 | /usr/lib/libgc.so.1 \ 47 | /usr/lib/libstdc++.so.6 \ 48 | /usr/lib/libgcc_s.so.1 \ 49 | /usr/lib/libuv.so.1 \ 50 | /usr/lib/ 51 | COPY --from=build \ 52 | /usr/local/lib/libre2.so.0 \ 53 | /usr/local/lib/libre2.so.0 54 | 55 | # Copy in the executable 56 | COPY --from=build \ 57 | /root/project-build/dinosaur-build-out /dinosaur-build-out 58 | 59 | RUN apk --update add go socat netcat-openbsd bash git vim 60 | ENV GOROOT /usr/lib/go 61 | ENV GOPATH /go 62 | ENV PATH /go/bin:$PATH 63 | RUN ["go", "get", "github.com/tomasen/fcgi_client"] 64 | 65 | ADD ./benchmark/go-fcgi /go/fcgi 66 | WORKDIR /go/fcgi 67 | 68 | RUN ["go", "build"] 69 | 70 | 71 | ENTRYPOINT ["bash", "-c"] 72 | ENV ROUTER_MODE FCGI 73 | CMD ["socat UNIX-LISTEN:/tmp/app.socket,fork,max-children=1,backlog=4096 EXEC:/dinosaur-build-out > fcgi.log 2> fcgi.error & ./fcgi 0.0.0.0:8080 /tmp/app.socket & tail -f fcgi.log"] 74 | # ENV ROUTER_MODE UVFCGI 75 | # CMD ["/dinosaur-build-out > fcgi.log 2> fcgi.error & sleep 1 && ./fcgi 0.0.0.0:8080 /tmp/app.socket & tail -f fcgi.log"] 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dinosaur 2 | Web "framework" for Scala Native with the power of [RFC 3875: The Common Gateway Interface](https://tools.ietf.org/html/rfc3875). 3 | 4 | ## what does it do? 5 | The CGI protocol is awesomely trivial: no threads, no network, no sockets, just STDIN, STDOUT, and environment variables--which happens to align with the bare-metal power of Scala Native. Dinosaur provides basic utilities for working with these primitives, but it also provides a straightforward Router API that should be familiar to anyone who's worked with Node, Flask, Sinatra, or the like. 6 | 7 | But that's not all -- Dinosaur provides a Dockerfile for reproducible, containerized builds of your Scala Native app, as well as a built-in Apache httpd web server. 8 | 9 | ## example code 10 | ```scala 11 | package io.dinosaur.main 12 | import io.dinosaur._ 13 | 14 | object main { 15 | def main(args: Array[String]): Unit = { 16 | Router.init() 17 | .get("/") { "

Hello World!

" } 18 | .get("/foo") { request => "bar" } 19 | .dispatch() 20 | } 21 | } 22 | ``` 23 | 24 | ## how do i get it? 25 | I'm still working on distributing Dinosaur as a Bintray package. Since that's not stable yet, I would recommend cloning this project and editing main.scala for now.(https://github.com/rwhaling/dinosaur-example-project). 26 | 27 | You will need Git and Docker. Once you have that: 28 | ```sh 29 | docker build -t dinosaur . 30 | docker run -d -p 80:80 dinosaur 31 | 32 | ``` 33 | 34 | Setting up Scala Native for local builds is outside the scope of this documentation, but well documented [on the main Scala Native site](http://www.scala-native.org/en/latest/user/setup.html). 35 | 36 | ## lean containers 37 | Although Scala Native produces tiny executables, the full SBT/JDK stack can push the size of an all-inclusive docker container up to about 600 MB. Dinosaur's Dockerfile uses multi-stage builds to separate the process into phases, and only copies binary artifcats into the final container. Note that this technique requires a recent version of Docker, 17.05 or newer. 38 | 39 | ## TODO 40 | * Working g8/sbt new integration 41 | * More examples 42 | * More documentation 43 | * More tests 44 | * Exception-based error code handling 45 | * Chunked transport for streaming 46 | * Static linking 47 | * JSON Parsing 48 | * HTTP Templating 49 | * Refined API, study existing Go and Rust models 50 | * Integrate with other web servers 51 | * Stress-testing and tuning Apache 52 | 53 | ## project status 54 | No, seriously, this isn't an elaborate joke. I did this because I love old-school UNIX systems coding, and I did this because I love Scala and am super-stoked about Scala Native. I've also been thinking a lot about what constitutes "vanilla" Scala style, and about ergonomics for an approachable web micro-framework, all of which inform the design of this project. 55 | 56 | That said, Scala Native is a *very* young project, and this is really purely speculative, research-quality, pre-release code for now. That said, I'd welcome outside contributions, issues, questions or comments. 57 | -------------------------------------------------------------------------------- /mpm.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Server-Pool Management (MPM specific) 3 | # 4 | 5 | # 6 | # PidFile: The file in which the server should record its process 7 | # identification number when it starts. 8 | # 9 | # Note that this is the default PidFile for most MPMs. 10 | # 11 | 12 | PidFile "/run/apache2/httpd.pid" 13 | 14 | 15 | # 16 | # Only one of the below sections will be relevant on your 17 | # installed httpd. Use "apachectl -l" to find out the 18 | # active mpm. 19 | # 20 | 21 | # prefork MPM 22 | # StartServers: number of server processes to start 23 | # MinSpareServers: minimum number of server processes which are kept spare 24 | # MaxSpareServers: maximum number of server processes which are kept spare 25 | # MaxRequestWorkers: maximum number of server processes allowed to start 26 | # MaxConnectionsPerChild: maximum number of connections a server process serves 27 | # before terminating 28 | 29 | StartServers 50 30 | MinSpareServers 50 31 | MaxSpareServers 50 32 | MaxRequestWorkers 250 33 | MaxConnectionsPerChild 0 34 | 35 | 36 | # worker MPM 37 | # StartServers: initial number of server processes to start 38 | # MinSpareThreads: minimum number of worker threads which are kept spare 39 | # MaxSpareThreads: maximum number of worker threads which are kept spare 40 | # ThreadsPerChild: constant number of worker threads in each server process 41 | # MaxRequestWorkers: maximum number of worker threads 42 | # MaxConnectionsPerChild: maximum number of connections a server process serves 43 | # before terminating 44 | 45 | StartServers 3 46 | MinSpareThreads 75 47 | MaxSpareThreads 250 48 | ThreadsPerChild 25 49 | MaxRequestWorkers 400 50 | MaxConnectionsPerChild 0 51 | 52 | 53 | # event MPM 54 | # StartServers: initial number of server processes to start 55 | # MinSpareThreads: minimum number of worker threads which are kept spare 56 | # MaxSpareThreads: maximum number of worker threads which are kept spare 57 | # ThreadsPerChild: constant number of worker threads in each server process 58 | # MaxRequestWorkers: maximum number of worker threads 59 | # MaxConnectionsPerChild: maximum number of connections a server process serves 60 | # before terminating 61 | 62 | StartServers 3 63 | MinSpareThreads 75 64 | MaxSpareThreads 250 65 | ThreadsPerChild 25 66 | MaxRequestWorkers 400 67 | MaxConnectionsPerChild 0 68 | 69 | 70 | # NetWare MPM 71 | # ThreadStackSize: Stack size allocated for each worker thread 72 | # StartThreads: Number of worker threads launched at server startup 73 | # MinSpareThreads: Minimum number of idle threads, to handle request spikes 74 | # MaxSpareThreads: Maximum number of idle threads 75 | # MaxThreads: Maximum number of worker threads alive at the same time 76 | # MaxConnectionsPerChild: Maximum number of connections a thread serves. It 77 | # is recommended that the default value of 0 be set 78 | # for this directive on NetWare. This will allow the 79 | # thread to continue to service requests indefinitely. 80 | 81 | ThreadStackSize 65536 82 | StartThreads 250 83 | MinSpareThreads 25 84 | MaxSpareThreads 250 85 | MaxThreads 1000 86 | MaxConnectionsPerChild 0 87 | 88 | 89 | # OS/2 MPM 90 | # StartServers: Number of server processes to maintain 91 | # MinSpareThreads: Minimum number of idle threads per process, 92 | # to handle request spikes 93 | # MaxSpareThreads: Maximum number of idle threads per process 94 | # MaxConnectionsPerChild: Maximum number of connections per server process 95 | 96 | StartServers 2 97 | MinSpareThreads 5 98 | MaxSpareThreads 10 99 | MaxConnectionsPerChild 0 100 | 101 | 102 | # WinNT MPM 103 | # ThreadsPerChild: constant number of worker threads in the server process 104 | # MaxConnectionsPerChild: maximum number of connections a server process serves 105 | 106 | ThreadsPerChild 150 107 | MaxConnectionsPerChild 0 108 | 109 | 110 | # The maximum number of free Kbytes that every allocator is allowed 111 | # to hold without calling free(). In threaded MPMs, every thread has its own 112 | # allocator. When not set, or when set to zero, the threshold will be set to 113 | # unlimited. 114 | 115 | MaxMemFree 2048 116 | 117 | 118 | MaxMemFree 100 119 | 120 | -------------------------------------------------------------------------------- /benchmark/python-cgi/mpm.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Server-Pool Management (MPM specific) 3 | # 4 | 5 | # 6 | # PidFile: The file in which the server should record its process 7 | # identification number when it starts. 8 | # 9 | # Note that this is the default PidFile for most MPMs. 10 | # 11 | 12 | PidFile "/run/apache2/httpd.pid" 13 | 14 | 15 | # 16 | # Only one of the below sections will be relevant on your 17 | # installed httpd. Use "apachectl -l" to find out the 18 | # active mpm. 19 | # 20 | 21 | # prefork MPM 22 | # StartServers: number of server processes to start 23 | # MinSpareServers: minimum number of server processes which are kept spare 24 | # MaxSpareServers: maximum number of server processes which are kept spare 25 | # MaxRequestWorkers: maximum number of server processes allowed to start 26 | # MaxConnectionsPerChild: maximum number of connections a server process serves 27 | # before terminating 28 | 29 | StartServers 50 30 | MinSpareServers 50 31 | MaxSpareServers 50 32 | MaxRequestWorkers 250 33 | MaxConnectionsPerChild 0 34 | 35 | 36 | # worker MPM 37 | # StartServers: initial number of server processes to start 38 | # MinSpareThreads: minimum number of worker threads which are kept spare 39 | # MaxSpareThreads: maximum number of worker threads which are kept spare 40 | # ThreadsPerChild: constant number of worker threads in each server process 41 | # MaxRequestWorkers: maximum number of worker threads 42 | # MaxConnectionsPerChild: maximum number of connections a server process serves 43 | # before terminating 44 | 45 | StartServers 3 46 | MinSpareThreads 75 47 | MaxSpareThreads 250 48 | ThreadsPerChild 25 49 | MaxRequestWorkers 400 50 | MaxConnectionsPerChild 0 51 | 52 | 53 | # event MPM 54 | # StartServers: initial number of server processes to start 55 | # MinSpareThreads: minimum number of worker threads which are kept spare 56 | # MaxSpareThreads: maximum number of worker threads which are kept spare 57 | # ThreadsPerChild: constant number of worker threads in each server process 58 | # MaxRequestWorkers: maximum number of worker threads 59 | # MaxConnectionsPerChild: maximum number of connections a server process serves 60 | # before terminating 61 | 62 | StartServers 3 63 | MinSpareThreads 75 64 | MaxSpareThreads 250 65 | ThreadsPerChild 25 66 | MaxRequestWorkers 400 67 | MaxConnectionsPerChild 0 68 | 69 | 70 | # NetWare MPM 71 | # ThreadStackSize: Stack size allocated for each worker thread 72 | # StartThreads: Number of worker threads launched at server startup 73 | # MinSpareThreads: Minimum number of idle threads, to handle request spikes 74 | # MaxSpareThreads: Maximum number of idle threads 75 | # MaxThreads: Maximum number of worker threads alive at the same time 76 | # MaxConnectionsPerChild: Maximum number of connections a thread serves. It 77 | # is recommended that the default value of 0 be set 78 | # for this directive on NetWare. This will allow the 79 | # thread to continue to service requests indefinitely. 80 | 81 | ThreadStackSize 65536 82 | StartThreads 250 83 | MinSpareThreads 25 84 | MaxSpareThreads 250 85 | MaxThreads 1000 86 | MaxConnectionsPerChild 0 87 | 88 | 89 | # OS/2 MPM 90 | # StartServers: Number of server processes to maintain 91 | # MinSpareThreads: Minimum number of idle threads per process, 92 | # to handle request spikes 93 | # MaxSpareThreads: Maximum number of idle threads per process 94 | # MaxConnectionsPerChild: Maximum number of connections per server process 95 | 96 | StartServers 2 97 | MinSpareThreads 5 98 | MaxSpareThreads 10 99 | MaxConnectionsPerChild 0 100 | 101 | 102 | # WinNT MPM 103 | # ThreadsPerChild: constant number of worker threads in the server process 104 | # MaxConnectionsPerChild: maximum number of connections a server process serves 105 | 106 | ThreadsPerChild 150 107 | MaxConnectionsPerChild 0 108 | 109 | 110 | # The maximum number of free Kbytes that every allocator is allowed 111 | # to hold without calling free(). In threaded MPMs, every thread has its own 112 | # allocator. When not set, or when set to zero, the threshold will be set to 113 | # unlimited. 114 | 115 | MaxMemFree 2048 116 | 117 | 118 | MaxMemFree 100 119 | 120 | -------------------------------------------------------------------------------- /src/main/scala/Dinosaur.scala: -------------------------------------------------------------------------------- 1 | package io.dinosaur 2 | import scalanative.native._ 3 | import scalanative.posix.unistd 4 | import io.dinosaur.CgiUtils._ 5 | import io.dinosaur.FastCGIUtils._ 6 | 7 | sealed trait RouterMode 8 | case object CGIMode extends RouterMode 9 | case object FCGIMode extends RouterMode 10 | case object UVFCGIMode extends RouterMode 11 | 12 | case class Handler( 13 | method : Method, 14 | pattern: Seq[String], 15 | handler: Request => Response 16 | ) { 17 | def this(method: Method, pattern:String, handler: Request => Response) = { 18 | this(method, CgiUtils.parsePathInfo(pattern),handler) 19 | } 20 | } 21 | 22 | trait Router { 23 | def handle(method: Method, path:String)(f: Request => Response):Router 24 | def get(path:String)(f: Request => Response):Router = handle(GET, path)(f) 25 | def post(path:String)(f: Request => Response):Router = handle(POST, path)(f) 26 | def put(path:String)(f: Request => Response):Router = handle(PUT, path)(f) 27 | def delete(path:String)(f: Request => Response):Router = handle(DELETE, path)(f) 28 | def dispatch(): Unit 29 | } 30 | 31 | case class FastCGIRouter(handlers:Seq[Handler]) extends Router { 32 | def handle(method: Method, path:String)(f: Request => Response):Router = { 33 | return FastCGIRouter(Seq()) 34 | } 35 | def dispatch(): Unit = { 36 | val header_buffer = stackalloc[Byte](8) 37 | val body_buffer = stackalloc[Byte](2048) 38 | System.err.println("reading from STDIN") 39 | var req_count = 0 40 | // var open_reqs = scala.collection.mutable.Set[Int]() 41 | while (true) { 42 | val header_read = unistd.read(unistd.STDIN_FILENO, header_buffer,8) 43 | if (header_read == 0) { 44 | System.err.println(s"pipe closed, exiting after serving $req_count requests") 45 | System.exit(0) 46 | } else if (header_read < 8) { 47 | System.err.println(s"Warning: read $header_read bytes for record header, expected 8") 48 | } 49 | val header = readHeader(header_buffer,0) 50 | // open_reqs += header.reqId 51 | // System.err.println(header) 52 | val content_read = unistd.read(unistd.STDIN_FILENO,body_buffer,header.length + header.padding) 53 | if (content_read < (header.length + header.padding)) { 54 | System.err.println(s"Warning: read $content_read bytes for record content, expected ${header.length + header.padding}") 55 | } 56 | // System.err.println(s"request ${header.reqId}: read $header_read bytes header type ${header.rec_type} and $content_read body from stdin") 57 | if ((header.rec_type == FCGI_STDIN) && (header.length == 0) ) { 58 | // System.err.println(s"sending response to request ${header.reqId} : ${req_count} total processed") 59 | 60 | // total hack work for now. 61 | // will fill in implementation once architecture is stabilized 62 | writeResponse(header.reqId, "Content-type: text/html\r\n\r\nhello") 63 | req_count += 1 64 | if (req_count >= 1000) { // TODO: parameterize 65 | System.err.println("done") 66 | stdio.fclose(stdio.stdout) 67 | stdio.fclose(stdio.stdin) 68 | System.err.println("closing out pipe, exiting") 69 | System.exit(0) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | case class CGIRouter(handlers:Seq[Handler]) extends Router { 77 | def handle(method: Method, path:String)(f: Request => Response):Router = { 78 | val new_handler = Handler(method, CgiUtils.parsePathInfo(path), f) 79 | return CGIRouter(Seq(new_handler) ++ this.handlers) 80 | } 81 | 82 | def dispatch(): Unit = { 83 | val request = Router.parseRequest() 84 | val matches = for ( h @ Handler(method, pattern, handler) <- this.handlers 85 | if request.method() == method 86 | if request.pathInfo().startsWith(pattern)) yield h 87 | val bestHandler = matches.maxBy( _.pattern.size ) 88 | val response = bestHandler.handler(request) 89 | for ( (k,v) <- response.inferHeaders ) { 90 | System.out.println(k + ": " + v) 91 | } 92 | System.out.println() 93 | System.out.println(response.bodyToString) 94 | } 95 | } 96 | 97 | object Router { 98 | def parseRequest():Request = { 99 | val scriptName = env(c"SCRIPT_NAME") 100 | val pathInfo = parsePathInfo(env(c"PATH_INFO")) 101 | val queryString = parseQueryString(env(c"QUERY_STRING")) 102 | val method = env(c"METHOD") match { 103 | case "GET" => GET 104 | case "POST" => POST 105 | case "PUT" => PUT 106 | case "DELETE" => DELETE 107 | case "HEAD" => HEAD 108 | case "OPTIONS"=> OPTIONS 109 | case "PATCH" => PATCH 110 | case _ => GET 111 | } 112 | val request = Request(() => method, () => pathInfo, queryString) 113 | request 114 | } 115 | 116 | def init():Router = { 117 | val errorResponse = Response(StringBody("No path matched the request")) 118 | val errorHandler = Handler(GET,List(), (_) => errorResponse) 119 | 120 | val debugHandler = Handler(GET,List("debug"), (request) => { 121 | Response(StringBody(request.toString)) 122 | }) 123 | 124 | val mode = CgiUtils.env(c"ROUTER_MODE") match { 125 | case "FCGI" => FCGIMode 126 | case "UVFCGI" => UVFCGIMode 127 | case _ => CGIMode 128 | } 129 | 130 | val handlers:Seq[Handler] = List(debugHandler, errorHandler) 131 | mode match { 132 | case CGIMode => CGIRouter(handlers) 133 | case FCGIMode => FastCGIRouter(handlers) 134 | case UVFCGIMode => UVFCGIRouter(handlers) 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/scala/FastCgiUtils.scala: -------------------------------------------------------------------------------- 1 | package io.dinosaur 2 | import scalanative.native._ 3 | import scalanative.posix.unistd 4 | 5 | sealed trait RequestType 6 | case object FCGI_BEGIN_REQUEST extends RequestType 7 | case object FCGI_ABORT_REQUEST extends RequestType 8 | case object FCGI_END_REQUEST extends RequestType 9 | case object FCGI_PARAMS extends RequestType 10 | case object FCGI_STDIN extends RequestType 11 | case object FCGI_STDOUT extends RequestType 12 | case object FCGI_STDERR extends RequestType 13 | case object FCGI_DATA extends RequestType 14 | case object FCGI_GET_VALUES extends RequestType 15 | case object FCGI_GET_VALUES_RESULT extends RequestType 16 | case object FCGI_UNKNOWN_TYPE extends RequestType 17 | 18 | case class RecordHeader(version:Int, rec_type:RequestType, reqId:Int, length:Int, padding:Int) 19 | 20 | object FastCGIUtils { 21 | def readHeader(input: Ptr[Byte], offset:Long): RecordHeader = { 22 | val version = input(0 + offset) & 0xFF 23 | val rec_type = (input(1 + offset) & 0xFF) match { 24 | case 0 => FCGI_UNKNOWN_TYPE 25 | case 1 => FCGI_BEGIN_REQUEST 26 | case 2 => FCGI_ABORT_REQUEST 27 | case 3 => FCGI_END_REQUEST 28 | case 4 => FCGI_PARAMS 29 | case 5 => FCGI_STDIN 30 | case 6 => FCGI_STDOUT 31 | case 7 => FCGI_STDERR 32 | case 8 => FCGI_DATA 33 | case 9 => FCGI_GET_VALUES 34 | case 10 => FCGI_GET_VALUES_RESULT 35 | case _ => FCGI_UNKNOWN_TYPE 36 | } 37 | val req_id_b1 = (input(2 + offset) & 0xFF) 38 | val req_id_b0 = (input(3 + offset) & 0xFF) 39 | val req_id = (req_id_b1 << 8) + (req_id_b0 & 0xFF) 40 | // System.err.println("req_id_b1: $req_id_b1 -- req_id_b0: $req_id_b0 -- req_id: $req_id") 41 | // println(s"length bytes: ${input(4 + offset) & 0xFF} ${input(5 + offset) & 0xFF}") 42 | val length = ((input(4 + offset) & 0xFF) << 8) + (input(5 + offset) & 0xFF) 43 | val padding = input(6 + offset) & 0xFF 44 | RecordHeader(version,rec_type,req_id,length,padding) 45 | } 46 | 47 | def readParam(byteArray: Ptr[Byte], arr_offset:Long, length:Long): (Ptr[Byte], Ptr[Byte], Long) = { 48 | val name_len_offset = arr_offset + 0 49 | val (name_len:Long, val_len_offset:Long) = if ((byteArray(name_len_offset) & 0x80) == 0) { 50 | val len = byteArray(name_len_offset) 51 | (len, arr_offset + 1) 52 | } else { 53 | val len = ((byteArray(name_len_offset) & 0x7F) << 24) + 54 | ((byteArray(name_len_offset + 1) & 0xFF) << 16) + 55 | ((byteArray(name_len_offset + 2) & 0xFF) << 8) + 56 | (byteArray(name_len_offset + 3) & 0xFF) 57 | (len, arr_offset + 4) 58 | } 59 | 60 | val (val_len:Long, content_offset:Long) = if ((byteArray(val_len_offset) & 0x80) == 0) { 61 | val len = byteArray(val_len_offset) 62 | (len, val_len_offset + 1) 63 | } else { 64 | val len = ((byteArray(val_len_offset) & 0x7F) << 24) + 65 | ((byteArray(val_len_offset + 1) & 0xFF) << 16) + 66 | ((byteArray(val_len_offset + 2) & 0xFF) << 8) + 67 | (byteArray(val_len_offset + 3) & 0xFF) 68 | (len, val_len_offset + 4) 69 | } 70 | val name = byteArray + content_offset 71 | val value = byteArray + content_offset + name_len 72 | val next_param_offset = content_offset + name_len + val_len 73 | (name, value, next_param_offset) 74 | } 75 | 76 | def readParams(byteArray: Ptr[Byte], arr_offset:Long, length:Long): Seq[(String,String)] = { 77 | var offset = arr_offset 78 | var results:Seq[(String,String)] = Seq() 79 | 80 | while (offset < (arr_offset + length)) { 81 | val name_length = if ((byteArray(offset) & 0x80) == 0) { 82 | byteArray(offset) 83 | } else { 84 | ((byteArray(offset) & 0x7F) << 24) + 85 | ((byteArray(offset + 1) & 0xFF) << 16) + 86 | ((byteArray(offset + 2) & 0xFF) << 8) + 87 | (byteArray(offset + 3) & 0xFF) 88 | } 89 | if (name_length <= 127) offset += 1 else offset += 4 90 | 91 | val value_length = if ((byteArray(offset) & 0x80) == 0) { 92 | byteArray(offset) 93 | } else { 94 | ((byteArray(offset) & 0x7F) << 24) + 95 | ((byteArray(offset + 1) & 0xFF) << 16) + 96 | ((byteArray(offset + 2) & 0xFF) << 8) + 97 | (byteArray(offset + 3) & 0xFF) 98 | } 99 | if (value_length <= 127) offset += 1 else offset += 4 100 | 101 | val name_array:CString = stackalloc[CChar](name_length + 1) 102 | string.memset(name_array, 0, name_length + 1) 103 | string.memcpy(name_array, byteArray + offset, name_length) 104 | val name = name_array.cast[CString] 105 | 106 | offset += name_length 107 | 108 | val value_array = stackalloc[CChar](value_length + 1) 109 | string.memset(value_array, 0, value_length + 1) 110 | string.memcpy(value_array,byteArray + offset, value_length) 111 | val value = value_array.cast[CString] 112 | 113 | offset += value_length 114 | // Zone { implicit z => 115 | // val n = fromCString(name) 116 | // val v = fromCString(value) 117 | // System.err.println(s"$name_length $value_length :: $n : $v @ $offset") 118 | // results = results :+ (n,v) 119 | // } 120 | } 121 | return results 122 | } 123 | 124 | def readAllHeaders(input: Ptr[Byte], input_size:Long): Seq[RecordHeader] = { 125 | var offset = 0 126 | var res:Vector[RecordHeader] = Vector() 127 | while (offset < input_size) { 128 | val header = readHeader(input, offset) 129 | 130 | if (header.rec_type == FCGI_PARAMS) { 131 | readParams(input, offset + 8, header.length) 132 | } 133 | 134 | offset += (8 + header.length + header.padding) 135 | // println(header) 136 | // println(s"offset $offset") 137 | res = res :+ header 138 | } 139 | res 140 | } 141 | 142 | def writeResponse(req_id: Int, response: Response): Unit = { 143 | val req_id_b1 = (req_id & 0xFF00) >> 8 144 | val req_id_b0 = req_id & 0xFF 145 | val responseBody = response.body match { 146 | case StringBody(s) => s 147 | case _ => "" 148 | } 149 | 150 | val len = responseBody.size 151 | val len_b1 = (len & 0xFF00) >> 8 152 | val len_b0 = len & 0xFF 153 | val endReqHeader = List(1,3, req_id_b1, req_id_b0 ) 154 | 155 | Zone { implicit z => 156 | stdio.printf(c"%c%c%c%c%c%c%c%c", 1, 6, req_id_b1, req_id_b0, len_b1, len_b0, 0, 0) 157 | stdio.printf(c"%s", toCString(responseBody)) 158 | stdio.printf(c"%c%c%c%c%c%c%c%c", 1, 6, req_id_b1, req_id_b0, 0, 0, 0, 0) 159 | stdio.printf(c"%c%c%c%c%c%c%c%c", 1, 3, req_id_b1, req_id_b0, 0, 8, 0, 0) 160 | stdio.printf(c"%c%c%c%c%c%c%c%c", 0, 0, 0, 0, 0, 0, 0, 0) 161 | } 162 | stdio.fflush(stdio.stdout) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/scala/UvUtils.scala: -------------------------------------------------------------------------------- 1 | package io.dinosaur 2 | import scalanative.native._ 3 | 4 | object UVFCGIRouter { 5 | import LibUV._ 6 | import FastCGIUtils._ 7 | val pipe_size = uv_handle_size(7) 8 | val loop:Loop = uv_default_loop() 9 | val write_req_size = uv_req_size(3) 10 | val shutdown_req_size = uv_req_size(4) 11 | var closing:PipeHandle = null 12 | 13 | def onConnect(server:PipeHandle, status:Int): Unit = { 14 | // println("connection received!") 15 | 16 | val client:PipeHandle = stdlib.malloc(pipe_size) 17 | uv_pipe_init(loop, client, 0) 18 | var r = uv_accept(server, client) 19 | // println(s"uv_accept returned $r") 20 | uv_read_start(client, onAllocCB, onReadCB) 21 | } 22 | val onConnectCB = CFunctionPtr.fromFunction2(onConnect) 23 | 24 | def onAlloc(pipe:PipeHandle, size:CSize, buffer:Ptr[Buffer]): Unit = { 25 | // println(s"allocing 2048 bytes") 26 | val buf = stdlib.malloc(2048) 27 | if (buf == null) { 28 | println("WARNING: malloc failed") 29 | sys.exit(1) 30 | } 31 | !buffer._1 = buf 32 | !buffer._2 = 2048 33 | } 34 | val onAllocCB = CFunctionPtr.fromFunction3(onAlloc) 35 | 36 | def onRead(pipe:PipeHandle, size:CSSize, buffer:Ptr[Buffer]): Unit = { 37 | // println(s"reading $size bytes") 38 | if (size >= 0) { 39 | // println(s"reading") 40 | // stdio.printf(c"read %d bytes: %.*s", size, size, !buffer._1) 41 | var position = 0 42 | // var frameOffsets:Seq[(Int,RecordHeader)] = Seq() 43 | var params:(Int,RecordHeader) = (0,null) 44 | var stdin:(Int,RecordHeader) = (0,null) 45 | var reqId = 1 46 | 47 | while (position < size) { 48 | val header = readHeader(!buffer._1,position) 49 | reqId = header.reqId 50 | // println(position,header) 51 | // frameOffsets = frameOffsets :+ (position,header) 52 | if (header.rec_type == FCGI_PARAMS & header.length > 0) 53 | params = (position,header) 54 | else if (header.rec_type == FCGI_STDIN & header.length > 0) 55 | stdin = (position, header) 56 | position += (8 + header.length + header.padding) 57 | } 58 | // println(s"read ${frameOffsets.length} frames ${frameOffsets} in $position bytes") 59 | 60 | // val debug = stdio.fopen(c"debug.in",c"a") 61 | // val wcount = stdio.fwrite(!buffer._1, 1, position, debug) 62 | // stdio.fclose(debug) 63 | // println(s"wrote $wcount bytes to debug.in") 64 | 65 | val write_req:WriteReq = stdlib.malloc(write_req_size).cast[Ptr[Ptr[Byte]]] 66 | !write_req = !buffer._1 67 | // stdlib.free(buffer.cast[Ptr[Byte]]) 68 | 69 | val resp = c"Content-type: text/html\r\n\r\nhello" 70 | 71 | !buffer._2 = makeResponse(reqId, resp, !write_req) 72 | uv_write(write_req, pipe, buffer, 1, onWriteCB) 73 | } else { 74 | // println("stopping reads on client") 75 | uv_read_stop(pipe) 76 | // println(s"mallocing $shutdown_req_size bytes for shutdownReq") 77 | val shutdownReq = stdlib.malloc(shutdown_req_size).cast[ShutdownReq] 78 | !shutdownReq = pipe 79 | // println(s"shutting down handle $pipe via request $shutdownReq") 80 | // closing = pipe 81 | uv_shutdown(shutdownReq, pipe, myShutdownCB) 82 | stdlib.free(!buffer._1) 83 | // uv_close(pipe, onCloseCB) 84 | // if (uv_is_closing(pipe)) { 85 | // println("pipe already closing") 86 | // } else { 87 | // println("about to call close from onRead") 88 | // uv_close(pipe, onCloseCB) 89 | // } 90 | // println("uv_close called") 91 | } 92 | // println("done with read") 93 | } 94 | val onReadCB = CFunctionPtr.fromFunction3(onRead) 95 | 96 | def makeResponse(req_id:Int, resp:CString, buf: Ptr[Byte]): Int = { 97 | val req_id_b1 = (req_id & 0xFF00) >> 8 98 | val req_id_b0 = req_id & 0xFF 99 | 100 | val len = string.strlen(resp).toInt 101 | val len_b1 = (len & 0xFF00) >> 8 102 | val len_b0 = len & 0xFF 103 | 104 | // Zone { implicit z => 105 | stdio.sprintf(buf, c"%c%c%c%c%c%c%c%c", 1, 6, req_id_b1, req_id_b0, len_b1, len_b0, 0, 0) 106 | stdio.sprintf(buf + 8, c"%s", resp) 107 | stdio.sprintf(buf + 8 + len, c"%c%c%c%c%c%c%c%c", 1, 6, req_id_b1, req_id_b0, 0, 0, 0, 0) 108 | stdio.sprintf(buf + 16 + len, c"%c%c%c%c%c%c%c%c", 1, 3, req_id_b1, req_id_b0, 0, 8, 0, 0) 109 | stdio.sprintf(buf + 24 + len, c"%c%c%c%c%c%c%c%c", 0, 0, 0, 0, 0, 0, 0, 0) 110 | // } 111 | 112 | // val debug = stdio.fopen(c"debug.out",c"a") 113 | // val wcount = stdio.fwrite(buf, 1, resp.size + 32, debug) 114 | // stdio.fclose(debug) 115 | // println(s"wrote $wcount bytes to debug.out") 116 | 117 | return len + 32 118 | } 119 | 120 | def onWrite(writeReq:WriteReq, status:Int): Unit = { 121 | if (status != 0) { 122 | println(s"write got status $status") 123 | } 124 | // println("Wrote succesfully") 125 | // stdio.fflush(stdio.stdout) 126 | stdlib.free(!writeReq) 127 | // should it stdlib.free(writeReq) as well? 128 | } 129 | val onWriteCB = CFunctionPtr.fromFunction2(onWrite) 130 | 131 | def myShutdownHandler(shutdownReq:ShutdownReq, status:Int): Unit = { 132 | // println(s"shutdown completed with status $status") 133 | // // val parsed_shutdownReq = shutdownReq.cast[Ptr[CStruct5[Ptr[Byte],Int,Ptr[Byte],Ptr[Byte],Ptr[Byte]]]] 134 | // // println("about to close") 135 | // // uv_close(!parsed_shutdownReq._5, onCloseCB) 136 | // // println("close called") 137 | // uv_close(!shutdownReq, onCloseCB) 138 | val pipe:PipeHandle = !shutdownReq 139 | if (uv_is_closing(pipe) != 0) { 140 | // println("pipe already closing") 141 | } else { 142 | // println("about to call close from myShutdownHandler") 143 | uv_close(pipe, onCloseCB) 144 | } 145 | 146 | } 147 | val myShutdownCB = CFunctionPtr.fromFunction2(myShutdownHandler) 148 | 149 | def onClose(handle:PipeHandle): Unit = { 150 | // println("onClose called") 151 | // closing = null 152 | } 153 | val onCloseCB = CFunctionPtr.fromFunction1(onClose) 154 | } 155 | 156 | case class UVFCGIRouter(handlers:Seq[Handler]) extends Router { 157 | import LibUV._ 158 | def handle(method: Method, path:String)(f: Request => Response):Router = { 159 | return UVFCGIRouter(Seq()) 160 | } 161 | 162 | def dispatch(): Unit = { 163 | println("Hello, libuv world!") 164 | val loop:Loop = uv_default_loop() 165 | val pipe_size = uv_handle_size(7) 166 | val pipe:PipeHandle = stackalloc[Byte](pipe_size) 167 | uv_pipe_init(loop, pipe, 0) 168 | var r = uv_pipe_bind(pipe, c"/tmp/app.socket") 169 | println(s"uv_pipe_bind returned $r") 170 | def cbf(pipe:PipeHandle, status:Int):Unit = { () } 171 | r = uv_listen(pipe, 4096, UVFCGIRouter.onConnectCB) 172 | println(s"uv_listen returned $r") 173 | r = uv_run(loop, 0) 174 | println(s"uv_run returned $r") 175 | } 176 | } 177 | 178 | @link("uv") 179 | @extern 180 | object LibUV { 181 | type PipeHandle = Ptr[Byte] 182 | type Loop = Ptr[Byte] 183 | type Buffer = CStruct2[Ptr[Byte],CSize] 184 | type WriteReq = Ptr[Ptr[Byte]] 185 | type ShutdownReq = Ptr[Ptr[Byte]] 186 | type Connection = Ptr[Byte] 187 | type ConnectionCB = CFunctionPtr2[PipeHandle,Int,Unit] 188 | type AllocCB = CFunctionPtr3[PipeHandle,CSize,Ptr[Buffer],Unit] 189 | type ReadCB = CFunctionPtr3[PipeHandle,CSSize,Ptr[Buffer],Unit] 190 | type WriteCB = CFunctionPtr2[WriteReq,Int,Unit] 191 | type ShutdownCB = CFunctionPtr2[ShutdownReq,Int,Unit] 192 | type CloseCB = CFunctionPtr1[PipeHandle,Unit] 193 | 194 | def uv_default_loop(): Loop = extern 195 | def uv_loop_size(): CSize = extern 196 | def uv_handle_size(h_type:Int): CSize = extern 197 | def uv_req_size(r_type:Int): CSize = extern 198 | def uv_pipe_init(loop:Loop, handle:PipeHandle, ipcFlag:Int ): Unit = extern 199 | def uv_pipe_bind(handle:PipeHandle, socketName:CString): Int = extern 200 | def uv_listen(handle:PipeHandle, backlog:Int, callback:ConnectionCB): Int = extern 201 | def uv_accept(server:PipeHandle, client:PipeHandle): Int = extern 202 | def uv_read_start(client:PipeHandle, allocCB:AllocCB, readCB:ReadCB): Int = extern 203 | def uv_write(writeReq:WriteReq, client:PipeHandle, bufs: Ptr[Buffer], numBufs: Int, writeCB:WriteCB): Int = extern 204 | def uv_read_stop(client:PipeHandle): Int = extern 205 | def uv_shutdown(shutdownReq:ShutdownReq, client:PipeHandle, shutdownCB:ShutdownCB): Int = extern 206 | def uv_close(handle:PipeHandle, closeCB: CloseCB): Unit = extern 207 | def uv_is_closing(handle:PipeHandle): Int = extern 208 | def uv_run(loop:Loop, runMode:Int): Int = extern 209 | } 210 | 211 | /* 212 | struct sockaddr_in 213 | 214 | uv_loop_t 215 | uv_write_t 216 | uv_tcp_t 217 | uv_stream_t 218 | uv_buf_t 219 | uv_handle_t 220 | 221 | uv_default_loop 222 | uv_tcp_init 223 | uv_ip4_addr 224 | uv_tcp_bind 225 | uv_listen 226 | uv_run 227 | uv_tcp_init 228 | uv_accept 229 | uv_read_start 230 | uv_write 231 | uv_strerror 232 | uv_close 233 | */ 234 | -------------------------------------------------------------------------------- /httpd.conf: -------------------------------------------------------------------------------- 1 | # 2 | # This is the main Apache HTTP server configuration file. It contains the 3 | # configuration directives that give the server its instructions. 4 | # See for detailed information. 5 | # In particular, see 6 | # 7 | # for a discussion of each configuration directive. 8 | # 9 | # Do NOT simply read the instructions in here without understanding 10 | # what they do. They're here only as hints or reminders. If you are unsure 11 | # consult the online docs. You have been warned. 12 | # 13 | # Configuration and logfile names: If the filenames you specify for many 14 | # of the server's control files begin with "/" (or "drive:/" for Win32), the 15 | # server will use that explicit path. If the filenames do *not* begin 16 | # with "/", the value of ServerRoot is prepended -- so "logs/access_log" 17 | # with ServerRoot set to "/usr/local/apache2" will be interpreted by the 18 | # server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log" 19 | # will be interpreted as '/logs/access_log'. 20 | 21 | # 22 | # ServerTokens 23 | # This directive configures what you return as the Server HTTP response 24 | # Header. The default is 'Full' which sends information about the OS-Type 25 | # and compiled in modules. 26 | # Set to one of: Full | OS | Minor | Minimal | Major | Prod 27 | # where Full conveys the most information, and Prod the least. 28 | # 29 | ServerTokens OS 30 | 31 | # 32 | # ServerRoot: The top of the directory tree under which the server's 33 | # configuration, error, and log files are kept. 34 | # 35 | # Do not add a slash at the end of the directory path. If you point 36 | # ServerRoot at a non-local disk, be sure to specify a local disk on the 37 | # Mutex directive, if file-based mutexes are used. If you wish to share the 38 | # same ServerRoot for multiple httpd daemons, you will need to change at 39 | # least PidFile. 40 | # 41 | ServerRoot /var/www 42 | 43 | # 44 | # Mutex: Allows you to set the mutex mechanism and mutex file directory 45 | # for individual mutexes, or change the global defaults 46 | # 47 | # Uncomment and change the directory if mutexes are file-based and the default 48 | # mutex file directory is not on a local disk or is not appropriate for some 49 | # other reason. 50 | # 51 | # Mutex default:/run/apache2 52 | 53 | # 54 | # Listen: Allows you to bind Apache to specific IP addresses and/or 55 | # ports, instead of the default. See also the 56 | # directive. 57 | # 58 | # Change this to Listen on specific IP addresses as shown below to 59 | # prevent Apache from glomming onto all bound IP addresses. 60 | # 61 | #Listen 12.34.56.78:80 62 | Listen 80 63 | 64 | # 65 | # Dynamic Shared Object (DSO) Support 66 | # 67 | # To be able to use the functionality of a module which was built as a DSO you 68 | # have to place corresponding `LoadModule' lines at this location so the 69 | # directives contained in it are actually available _before_ they are used. 70 | # Statically compiled modules (those listed by `httpd -l') do not need 71 | # to be loaded here. 72 | # 73 | # Example: 74 | # LoadModule foo_module modules/mod_foo.so 75 | # 76 | LoadModule authn_file_module modules/mod_authn_file.so 77 | #LoadModule authn_dbm_module modules/mod_authn_dbm.so 78 | #LoadModule authn_anon_module modules/mod_authn_anon.so 79 | #LoadModule authn_dbd_module modules/mod_authn_dbd.so 80 | #LoadModule authn_socache_module modules/mod_authn_socache.so 81 | LoadModule authn_core_module modules/mod_authn_core.so 82 | LoadModule authz_host_module modules/mod_authz_host.so 83 | LoadModule authz_groupfile_module modules/mod_authz_groupfile.so 84 | LoadModule authz_user_module modules/mod_authz_user.so 85 | #LoadModule authz_dbm_module modules/mod_authz_dbm.so 86 | #LoadModule authz_owner_module modules/mod_authz_owner.so 87 | #LoadModule authz_dbd_module modules/mod_authz_dbd.so 88 | LoadModule authz_core_module modules/mod_authz_core.so 89 | LoadModule access_compat_module modules/mod_access_compat.so 90 | LoadModule auth_basic_module modules/mod_auth_basic.so 91 | #LoadModule auth_form_module modules/mod_auth_form.so 92 | #LoadModule auth_digest_module modules/mod_auth_digest.so 93 | #LoadModule allowmethods_module modules/mod_allowmethods.so 94 | #LoadModule file_cache_module modules/mod_file_cache.so 95 | #LoadModule cache_module modules/mod_cache.so 96 | #LoadModule cache_disk_module modules/mod_cache_disk.so 97 | #LoadModule cache_socache_module modules/mod_cache_socache.so 98 | #LoadModule socache_shmcb_module modules/mod_socache_shmcb.so 99 | #LoadModule socache_dbm_module modules/mod_socache_dbm.so 100 | #LoadModule socache_memcache_module modules/mod_socache_memcache.so 101 | #LoadModule watchdog_module modules/mod_watchdog.so 102 | #LoadModule macro_module modules/mod_macro.so 103 | #LoadModule dbd_module modules/mod_dbd.so 104 | #LoadModule dumpio_module modules/mod_dumpio.so 105 | #LoadModule echo_module modules/mod_echo.so 106 | #LoadModule buffer_module modules/mod_buffer.so 107 | #LoadModule data_module modules/mod_data.so 108 | #LoadModule ratelimit_module modules/mod_ratelimit.so 109 | LoadModule reqtimeout_module modules/mod_reqtimeout.so 110 | #LoadModule ext_filter_module modules/mod_ext_filter.so 111 | #LoadModule request_module modules/mod_request.so 112 | #LoadModule include_module modules/mod_include.so 113 | LoadModule filter_module modules/mod_filter.so 114 | #LoadModule reflector_module modules/mod_reflector.so 115 | #LoadModule substitute_module modules/mod_substitute.so 116 | #LoadModule sed_module modules/mod_sed.so 117 | #LoadModule charset_lite_module modules/mod_charset_lite.so 118 | #LoadModule deflate_module modules/mod_deflate.so 119 | LoadModule mime_module modules/mod_mime.so 120 | LoadModule log_config_module modules/mod_log_config.so 121 | #LoadModule log_debug_module modules/mod_log_debug.so 122 | #LoadModule log_forensic_module modules/mod_log_forensic.so 123 | #LoadModule logio_module modules/mod_logio.so 124 | LoadModule env_module modules/mod_env.so 125 | #LoadModule mime_magic_module modules/mod_mime_magic.so 126 | #LoadModule expires_module modules/mod_expires.so 127 | LoadModule headers_module modules/mod_headers.so 128 | #LoadModule usertrack_module modules/mod_usertrack.so 129 | #LoadModule unique_id_module modules/mod_unique_id.so 130 | LoadModule setenvif_module modules/mod_setenvif.so 131 | LoadModule version_module modules/mod_version.so 132 | #LoadModule remoteip_module modules/mod_remoteip.so 133 | #LoadModule session_module modules/mod_session.so 134 | #LoadModule session_cookie_module modules/mod_session_cookie.so 135 | #LoadModule session_dbd_module modules/mod_session_dbd.so 136 | #LoadModule slotmem_shm_module modules/mod_slotmem_shm.so 137 | #LoadModule slotmem_plain_module modules/mod_slotmem_plain.so 138 | #LoadModule dialup_module modules/mod_dialup.so 139 | #LoadModule mpm_event_module modules/mod_mpm_event.so 140 | LoadModule mpm_prefork_module modules/mod_mpm_prefork.so 141 | #LoadModule mpm_worker_module modules/mod_mpm_worker.so 142 | LoadModule unixd_module modules/mod_unixd.so 143 | #LoadModule heartbeat_module modules/mod_heartbeat.so 144 | #LoadModule heartmonitor_module modules/mod_heartmonitor.so 145 | LoadModule status_module modules/mod_status.so 146 | LoadModule autoindex_module modules/mod_autoindex.so 147 | #LoadModule asis_module modules/mod_asis.so 148 | #LoadModule info_module modules/mod_info.so 149 | #LoadModule suexec_module modules/mod_suexec.so 150 | 151 | #LoadModule cgid_module modules/mod_cgid.so 152 | 153 | 154 | LoadModule cgi_module modules/mod_cgi.so 155 | 156 | #LoadModule vhost_alias_module modules/mod_vhost_alias.so 157 | #LoadModule negotiation_module modules/mod_negotiation.so 158 | LoadModule dir_module modules/mod_dir.so 159 | #LoadModule actions_module modules/mod_actions.so 160 | #LoadModule speling_module modules/mod_speling.so 161 | #LoadModule userdir_module modules/mod_userdir.so 162 | LoadModule alias_module modules/mod_alias.so 163 | #LoadModule rewrite_module modules/mod_rewrite.so 164 | 165 | LoadModule negotiation_module modules/mod_negotiation.so 166 | 167 | 168 | # 169 | # If you wish httpd to run as a different user or group, you must run 170 | # httpd as root initially and it will switch. 171 | # 172 | # User/Group: The name (or #number) of the user/group to run httpd as. 173 | # It is usually good practice to create a dedicated user and group for 174 | # running httpd, as with most system services. 175 | # 176 | User apache 177 | Group apache 178 | 179 | 180 | 181 | # 'Main' server configuration 182 | # 183 | # The directives in this section set up the values used by the 'main' 184 | # server, which responds to any requests that aren't handled by a 185 | # definition. These values also provide defaults for 186 | # any containers you may define later in the file. 187 | # 188 | # All of these directives may appear inside containers, 189 | # in which case these default settings will be overridden for the 190 | # virtual host being defined. 191 | # 192 | 193 | # 194 | # ServerAdmin: Your address, where problems with the server should be 195 | # e-mailed. This address appears on some server-generated pages, such 196 | # as error documents. e.g. admin@your-domain.com 197 | # 198 | ServerAdmin you@example.com 199 | 200 | # 201 | # Optionally add a line containing the server version and virtual host 202 | # name to server-generated pages (internal error documents, FTP directory 203 | # listings, mod_status and mod_info output etc., but not CGI generated 204 | # documents or custom error documents). 205 | # Set to "EMail" to also include a mailto: link to the ServerAdmin. 206 | # Set to one of: On | Off | EMail 207 | # 208 | ServerSignature On 209 | 210 | # 211 | # ServerName gives the name and port that the server uses to identify itself. 212 | # This can often be determined automatically, but we recommend you specify 213 | # it explicitly to prevent problems during startup. 214 | # 215 | # If your host doesn't have a registered DNS name, enter its IP address here. 216 | # 217 | #ServerName www.example.com:80 218 | 219 | # 220 | # Deny access to the entirety of your server's filesystem. You must 221 | # explicitly permit access to web content directories in other 222 | # blocks below. 223 | # 224 | 225 | AllowOverride none 226 | Require all denied 227 | 228 | 229 | # 230 | # Note that from this point forward you must specifically allow 231 | # particular features to be enabled - so if something's not working as 232 | # you might expect, make sure that you have specifically enabled it 233 | # below. 234 | # 235 | 236 | # 237 | # DocumentRoot: The directory out of which you will serve your 238 | # documents. By default, all requests are taken from this directory, but 239 | # symbolic links and aliases may be used to point to other locations. 240 | # 241 | DocumentRoot "/var/www/localhost/htdocs" 242 | 243 | # 244 | # Possible values for the Options directive are "None", "All", 245 | # or any combination of: 246 | # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews 247 | # 248 | # Note that "MultiViews" must be named *explicitly* --- "Options All" 249 | # doesn't give it to you. 250 | # 251 | # The Options directive is both complicated and important. Please see 252 | # http://httpd.apache.org/docs/2.4/mod/core.html#options 253 | # for more information. 254 | # 255 | Options Indexes FollowSymLinks 256 | 257 | # 258 | # AllowOverride controls what directives may be placed in .htaccess files. 259 | # It can be "All", "None", or any combination of the keywords: 260 | # AllowOverride FileInfo AuthConfig Limit 261 | # 262 | AllowOverride None 263 | 264 | # 265 | # Controls who can get stuff from this server. 266 | # 267 | Require all granted 268 | 269 | 270 | # 271 | # DirectoryIndex: sets the file that Apache will serve if a directory 272 | # is requested. 273 | # 274 | 275 | DirectoryIndex index.html 276 | 277 | 278 | # 279 | # The following lines prevent .htaccess and .htpasswd files from being 280 | # viewed by Web clients. 281 | # 282 | 283 | Require all denied 284 | 285 | 286 | # 287 | # ErrorLog: The location of the error log file. 288 | # If you do not specify an ErrorLog directive within a 289 | # container, error messages relating to that virtual host will be 290 | # logged here. If you *do* define an error logfile for a 291 | # container, that host's errors will be logged there and not here. 292 | # 293 | ErrorLog logs/error.log 294 | 295 | # 296 | # LogLevel: Control the number of messages logged to the error_log. 297 | # Possible values include: debug, info, notice, warn, error, crit, 298 | # alert, emerg. 299 | # 300 | LogLevel warn 301 | 302 | 303 | # 304 | # The following directives define some format nicknames for use with 305 | # a CustomLog directive (see below). 306 | # 307 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 308 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 309 | 310 | 311 | # You need to enable mod_logio.c to use %I and %O 312 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio 313 | 314 | 315 | # 316 | # The location and format of the access logfile (Common Logfile Format). 317 | # If you do not define any access logfiles within a 318 | # container, they will be logged here. Contrariwise, if you *do* 319 | # define per- access logfiles, transactions will be 320 | # logged therein and *not* in this file. 321 | # 322 | #CustomLog logs/access.log common 323 | 324 | # 325 | # If you prefer a logfile with access, agent, and referer information 326 | # (Combined Logfile Format) you can use the following directive. 327 | # 328 | CustomLog logs/access.log combined 329 | 330 | 331 | 332 | # 333 | # Redirect: Allows you to tell clients about documents that used to 334 | # exist in your server's namespace, but do not anymore. The client 335 | # will make a new request for the document at its new location. 336 | # Example: 337 | # Redirect permanent /foo http://www.example.com/bar 338 | 339 | # 340 | # Alias: Maps web paths into filesystem paths and is used to 341 | # access content that does not live under the DocumentRoot. 342 | # Example: 343 | # Alias /webpath /full/filesystem/path 344 | # 345 | # If you include a trailing / on /webpath then the server will 346 | # require it to be present in the URL. You will also likely 347 | # need to provide a section to allow access to 348 | # the filesystem path. 349 | 350 | # 351 | # ScriptAlias: This controls which directories contain server scripts. 352 | # ScriptAliases are essentially the same as Aliases, except that 353 | # documents in the target directory are treated as applications and 354 | # run by the server when requested rather than as documents sent to the 355 | # client. The same rules about trailing "/" apply to ScriptAlias 356 | # directives as to Alias. 357 | # 358 | ScriptAlias /cgi-bin/ "/var/www/localhost/cgi-bin/" 359 | 360 | 361 | 362 | 363 | # 364 | # ScriptSock: On threaded servers, designate the path to the UNIX 365 | # socket used to communicate with the CGI daemon of mod_cgid. 366 | # 367 | #Scriptsock cgisock 368 | 369 | 370 | # 371 | # "/var/www/localhost/cgi-bin" should be changed to whatever your ScriptAliased 372 | # CGI directory exists, if you have that configured. 373 | # 374 | 375 | AllowOverride None 376 | Options None 377 | Require all granted 378 | 379 | 380 | 381 | # 382 | # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied 383 | # backend servers which have lingering "httpoxy" defects. 384 | # 'Proxy' request header is undefined by the IETF, not listed by IANA 385 | # 386 | RequestHeader unset Proxy early 387 | 388 | 389 | 390 | # 391 | # TypesConfig points to the file containing the list of mappings from 392 | # filename extension to MIME-type. 393 | # 394 | TypesConfig /etc/apache2/mime.types 395 | 396 | # 397 | # AddType allows you to add to or override the MIME configuration 398 | # file specified in TypesConfig for specific file types. 399 | # 400 | #AddType application/x-gzip .tgz 401 | # 402 | # AddEncoding allows you to have certain browsers uncompress 403 | # information on the fly. Note: Not all browsers support this. 404 | # 405 | #AddEncoding x-compress .Z 406 | #AddEncoding x-gzip .gz .tgz 407 | # 408 | # If the AddEncoding directives above are commented-out, then you 409 | # probably should define those extensions to indicate media types: 410 | # 411 | AddType application/x-compress .Z 412 | AddType application/x-gzip .gz .tgz 413 | 414 | # 415 | # AddHandler allows you to map certain file extensions to "handlers": 416 | # actions unrelated to filetype. These can be either built into the server 417 | # or added with the Action directive (see below) 418 | # 419 | # To use CGI scripts outside of ScriptAliased directories: 420 | # (You will also need to add "ExecCGI" to the "Options" directive.) 421 | # 422 | #AddHandler cgi-script .cgi 423 | 424 | # For type maps (negotiated resources): 425 | #AddHandler type-map var 426 | 427 | # 428 | # Filters allow you to process content before it is sent to the client. 429 | # 430 | # To parse .shtml files for server-side includes (SSI): 431 | # (You will also need to add "Includes" to the "Options" directive.) 432 | # 433 | #AddType text/html .shtml 434 | #AddOutputFilter INCLUDES .shtml 435 | 436 | 437 | # 438 | # The mod_mime_magic module allows the server to use various hints from the 439 | # contents of the file itself to determine its type. The MIMEMagicFile 440 | # directive tells the module where the hint definitions are located. 441 | # 442 | 443 | MIMEMagicFile /etc/apache2/magic 444 | 445 | 446 | # 447 | # Customizable error responses come in three flavors: 448 | # 1) plain text 2) local redirects 3) external redirects 449 | # 450 | # Some examples: 451 | #ErrorDocument 500 "The server made a boo boo." 452 | #ErrorDocument 404 /missing.html 453 | #ErrorDocument 404 "/cgi-bin/missing_handler.pl" 454 | #ErrorDocument 402 http://www.example.com/subscription_info.html 455 | # 456 | 457 | # 458 | # MaxRanges: Maximum number of Ranges in a request before 459 | # returning the entire resource, or one of the special 460 | # values 'default', 'none' or 'unlimited'. 461 | # Default setting is to accept 200 Ranges. 462 | #MaxRanges unlimited 463 | 464 | # 465 | # EnableMMAP and EnableSendfile: On systems that support it, 466 | # memory-mapping or the sendfile syscall may be used to deliver 467 | # files. This usually improves server performance, but must 468 | # be turned off when serving from networked-mounted 469 | # filesystems or if support for these functions is otherwise 470 | # broken on your system. 471 | # Defaults: EnableMMAP On, EnableSendfile Off 472 | # 473 | #EnableMMAP off 474 | #EnableSendfile on 475 | 476 | # Load config files from the config directory "/etc/apache2/conf.d". 477 | # 478 | IncludeOptional /etc/apache2/conf.d/*.conf 479 | -------------------------------------------------------------------------------- /benchmark/python-cgi/httpd.conf: -------------------------------------------------------------------------------- 1 | # 2 | # This is the main Apache HTTP server configuration file. It contains the 3 | # configuration directives that give the server its instructions. 4 | # See for detailed information. 5 | # In particular, see 6 | # 7 | # for a discussion of each configuration directive. 8 | # 9 | # Do NOT simply read the instructions in here without understanding 10 | # what they do. They're here only as hints or reminders. If you are unsure 11 | # consult the online docs. You have been warned. 12 | # 13 | # Configuration and logfile names: If the filenames you specify for many 14 | # of the server's control files begin with "/" (or "drive:/" for Win32), the 15 | # server will use that explicit path. If the filenames do *not* begin 16 | # with "/", the value of ServerRoot is prepended -- so "logs/access_log" 17 | # with ServerRoot set to "/usr/local/apache2" will be interpreted by the 18 | # server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log" 19 | # will be interpreted as '/logs/access_log'. 20 | 21 | # 22 | # ServerTokens 23 | # This directive configures what you return as the Server HTTP response 24 | # Header. The default is 'Full' which sends information about the OS-Type 25 | # and compiled in modules. 26 | # Set to one of: Full | OS | Minor | Minimal | Major | Prod 27 | # where Full conveys the most information, and Prod the least. 28 | # 29 | ServerTokens OS 30 | 31 | # 32 | # ServerRoot: The top of the directory tree under which the server's 33 | # configuration, error, and log files are kept. 34 | # 35 | # Do not add a slash at the end of the directory path. If you point 36 | # ServerRoot at a non-local disk, be sure to specify a local disk on the 37 | # Mutex directive, if file-based mutexes are used. If you wish to share the 38 | # same ServerRoot for multiple httpd daemons, you will need to change at 39 | # least PidFile. 40 | # 41 | ServerRoot /var/www 42 | 43 | # 44 | # Mutex: Allows you to set the mutex mechanism and mutex file directory 45 | # for individual mutexes, or change the global defaults 46 | # 47 | # Uncomment and change the directory if mutexes are file-based and the default 48 | # mutex file directory is not on a local disk or is not appropriate for some 49 | # other reason. 50 | # 51 | # Mutex default:/run/apache2 52 | 53 | # 54 | # Listen: Allows you to bind Apache to specific IP addresses and/or 55 | # ports, instead of the default. See also the 56 | # directive. 57 | # 58 | # Change this to Listen on specific IP addresses as shown below to 59 | # prevent Apache from glomming onto all bound IP addresses. 60 | # 61 | #Listen 12.34.56.78:80 62 | Listen 80 63 | 64 | # 65 | # Dynamic Shared Object (DSO) Support 66 | # 67 | # To be able to use the functionality of a module which was built as a DSO you 68 | # have to place corresponding `LoadModule' lines at this location so the 69 | # directives contained in it are actually available _before_ they are used. 70 | # Statically compiled modules (those listed by `httpd -l') do not need 71 | # to be loaded here. 72 | # 73 | # Example: 74 | # LoadModule foo_module modules/mod_foo.so 75 | # 76 | LoadModule authn_file_module modules/mod_authn_file.so 77 | #LoadModule authn_dbm_module modules/mod_authn_dbm.so 78 | #LoadModule authn_anon_module modules/mod_authn_anon.so 79 | #LoadModule authn_dbd_module modules/mod_authn_dbd.so 80 | #LoadModule authn_socache_module modules/mod_authn_socache.so 81 | LoadModule authn_core_module modules/mod_authn_core.so 82 | LoadModule authz_host_module modules/mod_authz_host.so 83 | LoadModule authz_groupfile_module modules/mod_authz_groupfile.so 84 | LoadModule authz_user_module modules/mod_authz_user.so 85 | #LoadModule authz_dbm_module modules/mod_authz_dbm.so 86 | #LoadModule authz_owner_module modules/mod_authz_owner.so 87 | #LoadModule authz_dbd_module modules/mod_authz_dbd.so 88 | LoadModule authz_core_module modules/mod_authz_core.so 89 | LoadModule access_compat_module modules/mod_access_compat.so 90 | LoadModule auth_basic_module modules/mod_auth_basic.so 91 | #LoadModule auth_form_module modules/mod_auth_form.so 92 | #LoadModule auth_digest_module modules/mod_auth_digest.so 93 | #LoadModule allowmethods_module modules/mod_allowmethods.so 94 | #LoadModule file_cache_module modules/mod_file_cache.so 95 | #LoadModule cache_module modules/mod_cache.so 96 | #LoadModule cache_disk_module modules/mod_cache_disk.so 97 | #LoadModule cache_socache_module modules/mod_cache_socache.so 98 | #LoadModule socache_shmcb_module modules/mod_socache_shmcb.so 99 | #LoadModule socache_dbm_module modules/mod_socache_dbm.so 100 | #LoadModule socache_memcache_module modules/mod_socache_memcache.so 101 | #LoadModule watchdog_module modules/mod_watchdog.so 102 | #LoadModule macro_module modules/mod_macro.so 103 | #LoadModule dbd_module modules/mod_dbd.so 104 | #LoadModule dumpio_module modules/mod_dumpio.so 105 | #LoadModule echo_module modules/mod_echo.so 106 | #LoadModule buffer_module modules/mod_buffer.so 107 | #LoadModule data_module modules/mod_data.so 108 | #LoadModule ratelimit_module modules/mod_ratelimit.so 109 | LoadModule reqtimeout_module modules/mod_reqtimeout.so 110 | #LoadModule ext_filter_module modules/mod_ext_filter.so 111 | #LoadModule request_module modules/mod_request.so 112 | #LoadModule include_module modules/mod_include.so 113 | LoadModule filter_module modules/mod_filter.so 114 | #LoadModule reflector_module modules/mod_reflector.so 115 | #LoadModule substitute_module modules/mod_substitute.so 116 | #LoadModule sed_module modules/mod_sed.so 117 | #LoadModule charset_lite_module modules/mod_charset_lite.so 118 | #LoadModule deflate_module modules/mod_deflate.so 119 | LoadModule mime_module modules/mod_mime.so 120 | LoadModule log_config_module modules/mod_log_config.so 121 | #LoadModule log_debug_module modules/mod_log_debug.so 122 | #LoadModule log_forensic_module modules/mod_log_forensic.so 123 | #LoadModule logio_module modules/mod_logio.so 124 | LoadModule env_module modules/mod_env.so 125 | #LoadModule mime_magic_module modules/mod_mime_magic.so 126 | #LoadModule expires_module modules/mod_expires.so 127 | LoadModule headers_module modules/mod_headers.so 128 | #LoadModule usertrack_module modules/mod_usertrack.so 129 | #LoadModule unique_id_module modules/mod_unique_id.so 130 | LoadModule setenvif_module modules/mod_setenvif.so 131 | LoadModule version_module modules/mod_version.so 132 | #LoadModule remoteip_module modules/mod_remoteip.so 133 | #LoadModule session_module modules/mod_session.so 134 | #LoadModule session_cookie_module modules/mod_session_cookie.so 135 | #LoadModule session_dbd_module modules/mod_session_dbd.so 136 | #LoadModule slotmem_shm_module modules/mod_slotmem_shm.so 137 | #LoadModule slotmem_plain_module modules/mod_slotmem_plain.so 138 | #LoadModule dialup_module modules/mod_dialup.so 139 | #LoadModule mpm_event_module modules/mod_mpm_event.so 140 | LoadModule mpm_prefork_module modules/mod_mpm_prefork.so 141 | #LoadModule mpm_worker_module modules/mod_mpm_worker.so 142 | LoadModule unixd_module modules/mod_unixd.so 143 | #LoadModule heartbeat_module modules/mod_heartbeat.so 144 | #LoadModule heartmonitor_module modules/mod_heartmonitor.so 145 | LoadModule status_module modules/mod_status.so 146 | LoadModule autoindex_module modules/mod_autoindex.so 147 | #LoadModule asis_module modules/mod_asis.so 148 | #LoadModule info_module modules/mod_info.so 149 | #LoadModule suexec_module modules/mod_suexec.so 150 | 151 | #LoadModule cgid_module modules/mod_cgid.so 152 | 153 | 154 | LoadModule cgi_module modules/mod_cgi.so 155 | 156 | #LoadModule vhost_alias_module modules/mod_vhost_alias.so 157 | #LoadModule negotiation_module modules/mod_negotiation.so 158 | LoadModule dir_module modules/mod_dir.so 159 | #LoadModule actions_module modules/mod_actions.so 160 | #LoadModule speling_module modules/mod_speling.so 161 | #LoadModule userdir_module modules/mod_userdir.so 162 | LoadModule alias_module modules/mod_alias.so 163 | #LoadModule rewrite_module modules/mod_rewrite.so 164 | 165 | LoadModule negotiation_module modules/mod_negotiation.so 166 | 167 | 168 | # 169 | # If you wish httpd to run as a different user or group, you must run 170 | # httpd as root initially and it will switch. 171 | # 172 | # User/Group: The name (or #number) of the user/group to run httpd as. 173 | # It is usually good practice to create a dedicated user and group for 174 | # running httpd, as with most system services. 175 | # 176 | User apache 177 | Group apache 178 | 179 | 180 | 181 | # 'Main' server configuration 182 | # 183 | # The directives in this section set up the values used by the 'main' 184 | # server, which responds to any requests that aren't handled by a 185 | # definition. These values also provide defaults for 186 | # any containers you may define later in the file. 187 | # 188 | # All of these directives may appear inside containers, 189 | # in which case these default settings will be overridden for the 190 | # virtual host being defined. 191 | # 192 | 193 | # 194 | # ServerAdmin: Your address, where problems with the server should be 195 | # e-mailed. This address appears on some server-generated pages, such 196 | # as error documents. e.g. admin@your-domain.com 197 | # 198 | ServerAdmin you@example.com 199 | 200 | # 201 | # Optionally add a line containing the server version and virtual host 202 | # name to server-generated pages (internal error documents, FTP directory 203 | # listings, mod_status and mod_info output etc., but not CGI generated 204 | # documents or custom error documents). 205 | # Set to "EMail" to also include a mailto: link to the ServerAdmin. 206 | # Set to one of: On | Off | EMail 207 | # 208 | ServerSignature On 209 | 210 | # 211 | # ServerName gives the name and port that the server uses to identify itself. 212 | # This can often be determined automatically, but we recommend you specify 213 | # it explicitly to prevent problems during startup. 214 | # 215 | # If your host doesn't have a registered DNS name, enter its IP address here. 216 | # 217 | #ServerName www.example.com:80 218 | 219 | # 220 | # Deny access to the entirety of your server's filesystem. You must 221 | # explicitly permit access to web content directories in other 222 | # blocks below. 223 | # 224 | 225 | AllowOverride none 226 | Require all denied 227 | 228 | 229 | # 230 | # Note that from this point forward you must specifically allow 231 | # particular features to be enabled - so if something's not working as 232 | # you might expect, make sure that you have specifically enabled it 233 | # below. 234 | # 235 | 236 | # 237 | # DocumentRoot: The directory out of which you will serve your 238 | # documents. By default, all requests are taken from this directory, but 239 | # symbolic links and aliases may be used to point to other locations. 240 | # 241 | DocumentRoot "/var/www/localhost/htdocs" 242 | 243 | # 244 | # Possible values for the Options directive are "None", "All", 245 | # or any combination of: 246 | # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews 247 | # 248 | # Note that "MultiViews" must be named *explicitly* --- "Options All" 249 | # doesn't give it to you. 250 | # 251 | # The Options directive is both complicated and important. Please see 252 | # http://httpd.apache.org/docs/2.4/mod/core.html#options 253 | # for more information. 254 | # 255 | Options Indexes FollowSymLinks 256 | 257 | # 258 | # AllowOverride controls what directives may be placed in .htaccess files. 259 | # It can be "All", "None", or any combination of the keywords: 260 | # AllowOverride FileInfo AuthConfig Limit 261 | # 262 | AllowOverride None 263 | 264 | # 265 | # Controls who can get stuff from this server. 266 | # 267 | Require all granted 268 | 269 | 270 | # 271 | # DirectoryIndex: sets the file that Apache will serve if a directory 272 | # is requested. 273 | # 274 | 275 | DirectoryIndex index.html 276 | 277 | 278 | # 279 | # The following lines prevent .htaccess and .htpasswd files from being 280 | # viewed by Web clients. 281 | # 282 | 283 | Require all denied 284 | 285 | 286 | # 287 | # ErrorLog: The location of the error log file. 288 | # If you do not specify an ErrorLog directive within a 289 | # container, error messages relating to that virtual host will be 290 | # logged here. If you *do* define an error logfile for a 291 | # container, that host's errors will be logged there and not here. 292 | # 293 | ErrorLog logs/error.log 294 | 295 | # 296 | # LogLevel: Control the number of messages logged to the error_log. 297 | # Possible values include: debug, info, notice, warn, error, crit, 298 | # alert, emerg. 299 | # 300 | LogLevel warn 301 | 302 | 303 | # 304 | # The following directives define some format nicknames for use with 305 | # a CustomLog directive (see below). 306 | # 307 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 308 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 309 | 310 | 311 | # You need to enable mod_logio.c to use %I and %O 312 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio 313 | 314 | 315 | # 316 | # The location and format of the access logfile (Common Logfile Format). 317 | # If you do not define any access logfiles within a 318 | # container, they will be logged here. Contrariwise, if you *do* 319 | # define per- access logfiles, transactions will be 320 | # logged therein and *not* in this file. 321 | # 322 | #CustomLog logs/access.log common 323 | 324 | # 325 | # If you prefer a logfile with access, agent, and referer information 326 | # (Combined Logfile Format) you can use the following directive. 327 | # 328 | CustomLog logs/access.log combined 329 | 330 | 331 | 332 | # 333 | # Redirect: Allows you to tell clients about documents that used to 334 | # exist in your server's namespace, but do not anymore. The client 335 | # will make a new request for the document at its new location. 336 | # Example: 337 | # Redirect permanent /foo http://www.example.com/bar 338 | 339 | # 340 | # Alias: Maps web paths into filesystem paths and is used to 341 | # access content that does not live under the DocumentRoot. 342 | # Example: 343 | # Alias /webpath /full/filesystem/path 344 | # 345 | # If you include a trailing / on /webpath then the server will 346 | # require it to be present in the URL. You will also likely 347 | # need to provide a section to allow access to 348 | # the filesystem path. 349 | 350 | # 351 | # ScriptAlias: This controls which directories contain server scripts. 352 | # ScriptAliases are essentially the same as Aliases, except that 353 | # documents in the target directory are treated as applications and 354 | # run by the server when requested rather than as documents sent to the 355 | # client. The same rules about trailing "/" apply to ScriptAlias 356 | # directives as to Alias. 357 | # 358 | ScriptAlias /cgi-bin/ "/var/www/localhost/cgi-bin/" 359 | 360 | 361 | 362 | 363 | # 364 | # ScriptSock: On threaded servers, designate the path to the UNIX 365 | # socket used to communicate with the CGI daemon of mod_cgid. 366 | # 367 | #Scriptsock cgisock 368 | 369 | 370 | # 371 | # "/var/www/localhost/cgi-bin" should be changed to whatever your ScriptAliased 372 | # CGI directory exists, if you have that configured. 373 | # 374 | 375 | AllowOverride None 376 | Options None 377 | Require all granted 378 | 379 | 380 | 381 | # 382 | # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied 383 | # backend servers which have lingering "httpoxy" defects. 384 | # 'Proxy' request header is undefined by the IETF, not listed by IANA 385 | # 386 | RequestHeader unset Proxy early 387 | 388 | 389 | 390 | # 391 | # TypesConfig points to the file containing the list of mappings from 392 | # filename extension to MIME-type. 393 | # 394 | TypesConfig /etc/apache2/mime.types 395 | 396 | # 397 | # AddType allows you to add to or override the MIME configuration 398 | # file specified in TypesConfig for specific file types. 399 | # 400 | #AddType application/x-gzip .tgz 401 | # 402 | # AddEncoding allows you to have certain browsers uncompress 403 | # information on the fly. Note: Not all browsers support this. 404 | # 405 | #AddEncoding x-compress .Z 406 | #AddEncoding x-gzip .gz .tgz 407 | # 408 | # If the AddEncoding directives above are commented-out, then you 409 | # probably should define those extensions to indicate media types: 410 | # 411 | AddType application/x-compress .Z 412 | AddType application/x-gzip .gz .tgz 413 | 414 | # 415 | # AddHandler allows you to map certain file extensions to "handlers": 416 | # actions unrelated to filetype. These can be either built into the server 417 | # or added with the Action directive (see below) 418 | # 419 | # To use CGI scripts outside of ScriptAliased directories: 420 | # (You will also need to add "ExecCGI" to the "Options" directive.) 421 | # 422 | #AddHandler cgi-script .cgi 423 | 424 | # For type maps (negotiated resources): 425 | #AddHandler type-map var 426 | 427 | # 428 | # Filters allow you to process content before it is sent to the client. 429 | # 430 | # To parse .shtml files for server-side includes (SSI): 431 | # (You will also need to add "Includes" to the "Options" directive.) 432 | # 433 | #AddType text/html .shtml 434 | #AddOutputFilter INCLUDES .shtml 435 | 436 | 437 | # 438 | # The mod_mime_magic module allows the server to use various hints from the 439 | # contents of the file itself to determine its type. The MIMEMagicFile 440 | # directive tells the module where the hint definitions are located. 441 | # 442 | 443 | MIMEMagicFile /etc/apache2/magic 444 | 445 | 446 | # 447 | # Customizable error responses come in three flavors: 448 | # 1) plain text 2) local redirects 3) external redirects 449 | # 450 | # Some examples: 451 | #ErrorDocument 500 "The server made a boo boo." 452 | #ErrorDocument 404 /missing.html 453 | #ErrorDocument 404 "/cgi-bin/missing_handler.pl" 454 | #ErrorDocument 402 http://www.example.com/subscription_info.html 455 | # 456 | 457 | # 458 | # MaxRanges: Maximum number of Ranges in a request before 459 | # returning the entire resource, or one of the special 460 | # values 'default', 'none' or 'unlimited'. 461 | # Default setting is to accept 200 Ranges. 462 | #MaxRanges unlimited 463 | 464 | # 465 | # EnableMMAP and EnableSendfile: On systems that support it, 466 | # memory-mapping or the sendfile syscall may be used to deliver 467 | # files. This usually improves server performance, but must 468 | # be turned off when serving from networked-mounted 469 | # filesystems or if support for these functions is otherwise 470 | # broken on your system. 471 | # Defaults: EnableMMAP On, EnableSendfile Off 472 | # 473 | #EnableMMAP off 474 | #EnableSendfile on 475 | 476 | # Load config files from the config directory "/etc/apache2/conf.d". 477 | # 478 | IncludeOptional /etc/apache2/conf.d/*.conf 479 | --------------------------------------------------------------------------------