Skip to content

Commit

Permalink
Add file watcher (#20)
Browse files Browse the repository at this point in the history
* [ch] enable sync between volumes -

In order to update the docker container with a file change, a docker
rebuild is required. This will stop the need to run a rebuild.

* [ch] set docker timeout to `0` -

The default timeout for docker `stop` and `restart` is `10` seconds.
Please see [Docker
Docs](https://docs.docker.com/compose/reference/restart/).

* [ch] remove cmd to make entry file executable -

This has been recently pointed out that this is not required as it is a
php file.

* [ch] add best practice to `ENTRYPOINT` -

It is considered more performant to pass `ENTRYPOINT` and `CMD` an array
of the required command.

* [f] revert `ENTRYPOINT` to `ServerStub.php`

* [ch] implement restart of app on changes -

Separate demo and test with env specific Docker file and add script to
allow for app to be restarted where there are any changes
(create,delete,modify) made whilst deloping the application.

This is more proof of concept as this will only work on a Linux OS with
`inotify-tools` installed, but as a proof of concept it _seems_ to work
gracefully.

* [ch] Add CLI flags -

Allow `h`, `--host`, `p`, `--port` to be passed to start the server.

* [a] using GoLang to watch local dev -

This will start up the ReactPHP server in a child process within the
GoLang script. Killing the script will kill the child process as
intended allowing for a better development environment.

* [ch] improve initial output code -

refs #17
  • Loading branch information
NigelGreenway authored Jun 4, 2017
1 parent 585486e commit 2b63667
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 9 deletions.
126 changes: 126 additions & 0 deletions .build/react.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package main

import (
"log"
"os"
"os/exec"
"fmt"
"io/ioutil"
"encoding/json"

"github.com/fsnotify/fsnotify"
)

var (
settings struct {
ServerPath string `json:"server_path"`
Port string `json:"port"`
Host string `json:"host"`
DirectoriesToWatch []string `json:"directories_to_watch"`
}
ProcessID int = 0
)

func clearScreen() {
c := exec.Command("clear")
c.Stdout = os.Stdout
c.Run()
}

func startApp(pathToServer string, port string, host string) {
if port != "" {
port = "--port=" + port
}

if host != "" {
host = "--host=" + host
}

systemWithoutOutput("php", pathToServer, port, host)
}

func stopApp() {
killCommand := fmt.Sprintf("kill %d", ProcessID)
systemWithoutOutput("sh","-c", killCommand)
}

func restartApp(serverPath string, port string, host string) {
clearScreen()
fmt.Println("Restarting app")
stopApp()
startApp(serverPath, port, host)
}

func systemWithoutOutput(cmd string, arg ...string) {
command := exec.Command(cmd, arg...)
command.Stdout = os.Stdout
err := command.Start()

if err != nil {
log.Fatal(err)
}

ProcessID = command.Process.Pid
}

func getDevServerConfig () {
jsonFile, err := ioutil.ReadFile("./reactor.config.json")
if err != nil {
log.Fatal(err)
}

json.Unmarshal(jsonFile, &settings)
}

func initConsole (directoriesBeingWatch []string) {
fmt.Println("Running React App")
fmt.Println("Current directories/files being watched:")
for _, directory := range directoriesBeingWatch {
fmt.Println("\t",directory)
}
fmt.Println("")
}

func main() {
getDevServerConfig()

var (
serverPath string = settings.ServerPath
directoriesToWatch []string = settings.DirectoriesToWatch
port string = settings.Port
host string = settings.Host
)

startApp(serverPath, port, host)
initConsole(directoriesToWatch)

watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()

done := make(chan bool)

go func() {
for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
restartApp(serverPath, port, host)
}
case err := <-watcher.Errors:
log.Println("error:", err)
}
}
}()

for _, directory := range directoriesToWatch {
err = watcher.Add(directory)
if err != nil {
log.Fatal(err)
}
}

<-done
}
11 changes: 11 additions & 0 deletions .tools/docker-watch
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

composer start

echo "Watching \`./example\` and \`./src\`"

while true; do
filename=$(inotifywait -qre modify -e create -e move -e delete --format %f {./example,./src} )
printf "Restarted due to change in %s" $filename|awk '{split($0,a,"__"); print a[1]}'
docker restart -t 0 reactive_slim >> /dev/null
done
14 changes: 14 additions & 0 deletions .tools/local-watch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

php ./example/app-prod-mode.php &

echo "Watching \`./example\` and \`./src\`"

while true; do
filename=$(inotifywait -qre modify -e create -e move -e delete --format %f {./example,./src} )
printf "Restarted due to change in %s" $filename|awk '{split($0,a,"__"); print a[1]}'
kill `ps aux|grep app-prod-mode.php | \
head -n1 | \
awk -d='\t' '{print $2}'`
php ./example/app-prod-mode.php &
done
Binary file added .tools/reactor
Binary file not shown.
13 changes: 13 additions & 0 deletions .tools/test-docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh

# Build & run container
docker build -t reactive_slim_test -f ./Dockerfile.test .
docker run -d -p 1351:1351 --name reactive_tests reactive_slim_test

# run tests
./bin/phpspec r
./bin/phpunit -c ./test/phpunit.xml.dist

# tidy up
docker kill reactive_tests
docker rm -f reactive_tests
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ RUN apk update
RUN apk add --update zlib-dev
RUN docker-php-ext-install -j$(getconf _NPROCESSORS_ONLN) iconv
RUN rm -rf /tmp/*
RUN chmod +x ./test/Integration/ServerStub.php

EXPOSE 1351
EXPOSE 1337

ENTRYPOINT php ./test/Integration/ServerStub.php
ENTRYPOINT ["php", "./example/app-prod-mode.php"]
13 changes: 13 additions & 0 deletions Dockerfile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM php:7.0-alpine

COPY . /var/app
WORKDIR /var/app

RUN apk update
RUN apk add --update zlib-dev
RUN docker-php-ext-install -j$(getconf _NPROCESSORS_ONLN) iconv
RUN rm -rf /tmp/*

EXPOSE 1351

ENTRYPOINT ["php", "./test/Integration/ServerStub.php"]
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ After creating your [Slim Instance](https://www.slimframework.com/), pass it to

_Please see the [examples](/example) for more information or run `php ./example/app-dev-mode.php`._

If are you are running via PHP locally, you are able to pass the following flags to customise both the host and the port:

`-h 0.0.0.0` or `--host=0.0.0.0` and `-p 8686` or `--port=8686`

### Restarting your ReactApp

There are 3 ways to restart your application (only tested on Linux - Solus, kernel 4.9.30-29.lts currently):

- `sh ./.tools/local-watch` runs via local PHP installation (requires `inotify-tools` to be installed via package manager)
- `sh ./.tools/reactor` run via local PHP installation and requires the `reactor.config.json` config
- `sh ./.tools/docker-watch` run the app within a Docker container (requires `inotify-tools` to be installed via package manager)

### Extra options

`#withHost(<string>)` - The default host URL is `0.0.0.0` but is overridden by passing a string as the parameter
Expand Down
13 changes: 7 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,15 @@
"sort-packages": true
},
"scripts": {
"build": "docker build -t nigelgreenway:reactive-slim .",
"start": "docker run -d -p 1351:1351 --name reactive_slim nigelgreenway:reactive-slim",
"restart": "docker restart reactive_slim",
"stop": "docker stop reactive_slim",
"test": "composer start && phpspec r && phpunit -c ./test/phpunit.xml.dist ./test && composer stop",
"build": "docker build -t reactive-slim -f Dockerfile .",
"start": "docker run -v `pwd`:/var/app -d -p 1337:1337 --name reactive_slim reactive-slim",
"watch": "./.tools/docker-watch",
"restart": "docker restart -t 0 reactive_slim",
"stop": "docker stop -t 0 reactive_slim",
"test": "sh ./.tools/test-docker",
"check-style": "phpcs -p --standard=psr2-override.xml --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src",
"fix-style": "phpcbf -p --standard=psr2-override.xml --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src",
"clean": "rm -r ./{vendor,bin} && docker stop reactive_slim && docker rm reactive_slim",
"clean": "rm -r ./{vendor,bin} && docker stop -t 0 reactive_slim && docker rm reactive_slim",
"post-install-cmd": [
"captainhook install",
"composer build"
Expand Down
9 changes: 9 additions & 0 deletions reactor.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"server_path": "./example/app-dev-mode.php",
"port": "1337",
"host": "0.0.0.0",
"directories_to_watch": [
"./example/",
"./src/"
]
}
15 changes: 15 additions & 0 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function __construct(
string $directoryPath = null
) {
$this->isAValidDirectory($directoryPath);
$this->setDefaults();

$this->webRoot = $directoryPath;
$this->slimInstance = $slimInstance;
Expand Down Expand Up @@ -192,6 +193,20 @@ private function initialiseReactPHP()
$this->server = new HttpServer($socketServer);
}

/**
* @return void
*/
private function setDefaults()
{
$options = getopt('p::h::', ['port::', 'host::']);

$port = $options['p'] ?? $options['port'] ?? 1337;
$this->setPort((int) $port);

$host = $options['h'] ?? $options['host'] ?? '0.0.0.0';
$this->setHost($host);
}

/**
* @param string|null $directoryPath
*
Expand Down

0 comments on commit 2b63667

Please sign in to comment.