diff --git a/.dockerignore b/.dockerignore index 56b89f7..fea7610 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,8 @@ /public/build/images/glyphicons-* *.Dockerfile +docker-compose.* +Makefile ###> symfony/framework-bundle ### /.env.local diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..86cc40d --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: all rr fpm builtin apache + +%.Dockerfile: + docker build -f $@ -t symfony-demo:$(basename $@) . + +rr.Dockerfile: base.Dockerfile +fpm.Dockerfile: base.Dockerfile +builtin.Dockerfile: base.Dockerfile +apache.Dockerfile: base.Dockerfile + +rr: rr.Dockerfile +fpm: fpm.Dockerfile +builtin: builtin.Dockerfile +apache: apache.Dockerfile + +all: rr fpm builtin diff --git a/apache.Dockerfile b/apache.Dockerfile new file mode 100644 index 0000000..51b92cf --- /dev/null +++ b/apache.Dockerfile @@ -0,0 +1,24 @@ +FROM php:7.4-apache + +RUN apt-get update && apt-get -y install libicu-dev libzip-dev && \ + docker-php-ext-install bcmath intl opcache zip sockets; + +ENV APP_ENV=prod +ENV DATABASE_URL="sqlite:////var/db/app.db" +ENV MAILER_DSN="smtp://localhost:25" +ENV PATH=$PATH:/var/www/bin + +WORKDIR /var/www + +COPY --from=symfony-demo:base /var/www . +COPY --from=symfony-demo:base /var/db /var/db + +# Timezone +RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \ + echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini; + +RUN a2enmod rewrite +COPY ./docker/apache.conf /etc/apache2/sites-available/000-default.conf +RUN chown -R www-data:www-data /var/www + +CMD ["./bin/docker-init.sh", "apache2-foreground"] diff --git a/base.Dockerfile b/base.Dockerfile new file mode 100644 index 0000000..510b196 --- /dev/null +++ b/base.Dockerfile @@ -0,0 +1,27 @@ +FROM node:latest as assets +WORKDIR /var/www + +COPY ./assets/ ./assets/ +COPY ./package.json webpack.config.js yarn.lock ./ +RUN yarn install && yarn run build + +FROM php:7.4-alpine + +ENV APP_ENV=prod +ENV DATABASE_URL="sqlite:////var/db/app.db" +ENV MAILER_DSN="smtp://localhost:25" + +WORKDIR /var/www + +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +COPY . . +COPY --from=assets /var/www/public public + +RUN composer install --prefer-dist --no-progress --no-interaction --ignore-platform-reqs && \ + composer dump-autoload --optimize && \ + php bin/console cache:warmup + +RUN mkdir /var/db && \ + ./bin/console doctrine:schema:create && \ + ./bin/console doctrine:schema:update --force && \ + yes | APP_ENV=dev ./bin/console doctrine:fixtures:load diff --git a/builtin.Dockerfile b/builtin.Dockerfile index 33ebfe8..71c277c 100644 --- a/builtin.Dockerfile +++ b/builtin.Dockerfile @@ -1,11 +1,9 @@ -FROM node:latest as assets -WORKDIR /var/www - -COPY . . -RUN yarn install && yarn run build - FROM php:7.4-alpine +RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \ + docker-php-ext-install bcmath intl opcache zip sockets && \ + apk del --purge autoconf g++ make; + ENV APP_ENV=prod ENV DATABASE_URL="sqlite:////var/db/app.db" ENV MAILER_DSN="smtp://localhost:25" @@ -13,24 +11,8 @@ ENV PATH=$PATH:/var/www/bin WORKDIR /var/www -RUN mkdir /var/db - -RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \ - docker-php-ext-install bcmath intl opcache zip sockets && \ - apk del --purge autoconf g++ make; - -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer -COPY . . -COPY --from=assets /var/www/public public - -RUN composer install --prefer-dist --no-progress --no-interaction && \ - composer dump-autoload --optimize && \ - composer check-platform-reqs && \ - php bin/console cache:warmup - -RUN ./bin/console doctrine:schema:create && \ - ./bin/console doctrine:schema:update --force && \ - yes | APP_ENV=dev ./bin/console doctrine:fixtures:load +COPY --from=symfony-demo:base /var/www . +COPY --from=symfony-demo:base /var/db /var/db # Timezone RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \ diff --git a/docker-compose.locust.yml b/docker-compose.locust.yml index 917b11b..eba8fae 100644 --- a/docker-compose.locust.yml +++ b/docker-compose.locust.yml @@ -6,11 +6,42 @@ services: ports: - "8080:8089" volumes: - - ./locust:/mnt/locust + - ./:/mnt/locust command: -f /mnt/locust/locustfile.py --master -H http://master:8089 worker: image: locustio/locust volumes: - - ./locust:/mnt/locust + - ./:/mnt/locust command: -f /mnt/locust/locustfile.py --worker --master-host master + depends_on: + - master + + frontend-rr: + image: symfony-demo:rr + ports: + - 8081:8080 + + frontend-builtin: + image: symfony-demo:builtin + ports: + - 8082:8080 + + frontend-fpm: + image: nginx:latest + volumes: + - ./public:/var/www/public + - ./docker/symfony-demo.conf:/etc/nginx/conf.d/default.conf + - ./docker/upstream.conf:/etc/nginx/conf.d/upstream.conf + ports: + - 8083:8080 + depends_on: + - fpm + + fpm: + image: symfony-demo:fpm + + frontend-apache: + image: symfony-demo:apache + ports: + - 8084:80 diff --git a/docker/apache.conf b/docker/apache.conf new file mode 100644 index 0000000..4526c98 --- /dev/null +++ b/docker/apache.conf @@ -0,0 +1,10 @@ + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/public + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + diff --git a/docker/php-fpm.conf b/docker/php-fpm.conf new file mode 100644 index 0000000..b85fea0 --- /dev/null +++ b/docker/php-fpm.conf @@ -0,0 +1,8 @@ +[www] +listen = 0.0.0.0:9000 + +pm = dynamic +pm.max_children = 32 +pm.start_servers = 8 +pm.min_spare_servers = 4 +pm.max_spare_servers = 8 diff --git a/docker/symfony-demo.conf b/docker/symfony-demo.conf new file mode 100644 index 0000000..819ce96 --- /dev/null +++ b/docker/symfony-demo.conf @@ -0,0 +1,27 @@ +server { + listen 8080; + root /var/www/public; + + location / { + try_files $uri /index.php$is_args$args; + } + + location ~ ^/.+\.php(/|$) { + fastcgi_buffer_size 256k; + fastcgi_buffers 64 256k; + fastcgi_pass php-upstream; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + internal; + } + + location ~ \.php$ { + return 404; + } + + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; +} + diff --git a/docker/upstream.conf b/docker/upstream.conf new file mode 100644 index 0000000..f79b448 --- /dev/null +++ b/docker/upstream.conf @@ -0,0 +1 @@ +upstream php-upstream { server fpm:9000; } diff --git a/fpm.Dockerfile b/fpm.Dockerfile index 5c99b9a..682b57c 100644 --- a/fpm.Dockerfile +++ b/fpm.Dockerfile @@ -1,25 +1,23 @@ FROM php:7.4-fpm-alpine -ENV APP_ENV=prod -ENV DATABASE_URL="sqlite:////var/db/app.db" -ENV PATH=$PATH:/usr/src/app/bin - RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \ docker-php-ext-install bcmath intl opcache zip sockets && \ apk del --purge autoconf g++ make; -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer -COPY . . +ENV APP_ENV=prod +ENV DATABASE_URL="sqlite:////var/db/app.db" +ENV MAILER_DSN="smtp://localhost:25" +ENV PATH=$PATH:/var/www/bin + +WORKDIR /var/www -RUN composer install --no-dev --no-scripts --no-plugins --prefer-dist --no-progress --no-interaction && \ - composer dump-autoload --optimize && \ - composer check-platform-reqs && \ - php bin/console cache:warmup +COPY --from=symfony-demo:base /var/www . +COPY --from=symfony-demo:base /var/db /var/db # Timezone RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \ echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini; -WORKDIR /var/www +ADD ./docker/php-fpm.conf /usr/local/etc/php-fpm.d/symfony.conf CMD ["./bin/docker-init.sh", "php-fpm"] diff --git a/locustfile.py b/locustfile.py new file mode 100644 index 0000000..65c2017 --- /dev/null +++ b/locustfile.py @@ -0,0 +1,43 @@ +import time +from locust import HttpUser, task, between +from random import randint, choice, seed + +seed(3721) + +class BlogUser(HttpUser): + wait_time = between(1, 2.5) + + def on_start(self): + self.max_pages = 3 + self.tags = [ tag['name'] for tag in self.client.get("/en/api/tags").json() ] + self.queries = ["Lorem ipsum", "vitae velit", "Ubi est", "dolor"] + + @task(5) + def browse_tag(self): + tag = choice(self.tags) + self.client.get(f"/en/blog?tag={tag}") + + posts = self.client.get(f"/en/api/posts?tag={tag}").json() + self.browse_posts(posts, 3) + + @task(10) + def browse_task(self): + page = randint(1, self.max_pages) + + self.client.get(f"/en/blog/page/{page}") + posts = self.client.get(f"/en/api/posts?page={page}").json() + self.browse_posts(posts, 4) + + @task(2) + def search_task(self): + query = choice(self.queries) + posts = self.client.get(f"/en/api/search?q={query}").json() + self.browse_posts(posts, 2) + + def browse_posts(self, posts, count): + if len(posts) == 0: + return + + for _ in range(count): + post = choice(posts) + self.client.get(post['url']) diff --git a/rr.Dockerfile b/rr.Dockerfile index cf6bd1b..df03846 100644 --- a/rr.Dockerfile +++ b/rr.Dockerfile @@ -1,11 +1,9 @@ -FROM node:latest as assets -WORKDIR /var/www - -COPY . . -RUN yarn install && yarn run build - FROM php:7.4-alpine +RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \ + docker-php-ext-install bcmath intl opcache zip sockets && \ + apk del --purge autoconf g++ make; + ENV APP_ENV=prod ENV DATABASE_URL="sqlite:////var/db/app.db" ENV MAILER_DSN="smtp://localhost:25" @@ -13,29 +11,14 @@ ENV PATH=$PATH:/var/www/bin WORKDIR /var/www -RUN mkdir /var/db - -RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \ - docker-php-ext-install bcmath intl opcache zip sockets && \ - apk del --purge autoconf g++ make; - -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer -COPY . . -COPY --from=assets /var/www/public public - -RUN composer install --prefer-dist --no-progress --no-interaction && \ - composer dump-autoload --optimize && \ - composer check-platform-reqs && \ - php bin/console cache:warmup - -RUN ./bin/console doctrine:schema:create && \ - ./bin/console doctrine:schema:update --force && \ - yes | APP_ENV=dev ./bin/console doctrine:fixtures:load +COPY --from=symfony-demo:base /var/www . +COPY --from=symfony-demo:base /var/db /var/db # Timezone RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \ echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini; +# Get rr binary RUN ./vendor/bin/rr get-binary --location /usr/local/bin EXPOSE 8080 diff --git a/src/Controller/ApiController.php b/src/Controller/ApiController.php new file mode 100644 index 0000000..2efbfc8 --- /dev/null +++ b/src/Controller/ApiController.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Controller; + +use App\Entity\Comment; +use App\Entity\Post; +use App\Event\CommentCreatedEvent; +use App\Form\CommentType; +use App\Repository\PostRepository; +use App\Repository\TagRepository; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; + +/** + * Controller managing API. + * + * @Route("/api") + * + * @author Ryan Weaver + * @author Javier Eguiluz + */ +class ApiController extends AbstractController +{ + /** + * @Route("/posts", defaults={"_format"="json"}, methods="GET", name="api_index") + */ + public function index(Request $request, PostRepository $posts, TagRepository $tags): Response + { + $tag = null; + + if ($request->query->has('tag')) { + /** @var \App\Entity\Tag $tag */ + $tag = $tags->findOneBy(['name' => $request->query->get('tag')]); + } + + $latestPosts = $posts->findLatest($request->query->get('page', 1), $tag); + + $results = []; + foreach ($latestPosts->getResults() as $post) { + $results[] = $this->postToJson($post); + } + + return $this->json($results); + } + + /** + * @Route("/tags", defaults={"page": "1", "_format"="json"}, methods="GET", name="api_tags") + */ + public function tags(TagRepository $tags): Response + { + /** @var \App\Entity\Tag[] $tags */ + $tags = $tags->findAll(); + + $results = []; + foreach ($tags as $tag) { + $results[] = [ + 'name' => $tag->getName(), + ]; + } + + return $this->json($results); + } + + /** + * @Route("/search", defaults={"_format": "json"}, methods="GET", name="api_search") + */ + public function search(Request $request, PostRepository $posts): Response + { + $query = $request->query->get('q', ''); + $limit = $request->query->get('l', 10); + + $foundPosts = $posts->findBySearchQuery($query, $limit); + + $results = []; + foreach ($foundPosts as $post) { + $results[] = $this->postToJson($post); + } + + return $this->json($results); + } + + private function postToJson(Post $post): array + { + return [ + 'title' => htmlspecialchars($post->getTitle(), ENT_COMPAT | ENT_HTML5), + 'date' => $post->getPublishedAt()->format('M d, Y'), + 'author' => htmlspecialchars($post->getAuthor()->getFullName(), ENT_COMPAT | ENT_HTML5), + 'summary' => htmlspecialchars($post->getSummary(), ENT_COMPAT | ENT_HTML5), + 'url' => $this->generateUrl('blog_post', ['slug' => $post->getSlug()]), + ]; + } +}