Skip to content

Commit

Permalink
Allow hosting OpenBooks server at a subpath. (#34)
Browse files Browse the repository at this point in the history
* feature: Add support for hosting at sub path.

- The subpath is set via environment variable or cli
  argument.
- Updated readme files to show it's used.

* fix: minor wording change
  • Loading branch information
evan-buss committed Aug 23, 2021
1 parent ab8a7ce commit 3c426e9
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 56 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
**/node_modules/
**/build/
**/dist/
**/github/
openbooks.exe
openbooks
*.exe
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ COPY --from=build /go/src/openbooks .

EXPOSE 80
VOLUME [ "/books" ]
ENV BASE_PATH=/

ENTRYPOINT ["./openbooks", "server", "--dir", "/books", "--port", "80"]
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ Openbooks allows you to download ebooks from irc.irchighway.net quickly and easi
- Config to perist all eBook files to disk
- `docker run -p 8080:80 -v /home/evan/Downloads/books:/books evanbuss/openbooks --persist`

### Setting the Base Path

OpenBooks server doesn't have to be hosted at the root of your webserver. The basepath value allows you to host it behind a reverse proxy. The base path value must have opening and closing forward slashes (default "/").

- Docker
- `docker run -p 8080:80 -e BASE_PATH=/openbooks/ evanbuss/openbooks`
- Binary
- `./openbooks server --basepath /openbooks/`

## Development

### Install the dependencies
Expand Down
12 changes: 11 additions & 1 deletion cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ func init() {
serverCmd.Flags().StringP("port", "p", "5228", "Set the local network port for browser mode.")
serverCmd.Flags().StringP("dir", "d", os.TempDir(), "The directory where eBooks are saved when persist enabled.")
serverCmd.Flags().Bool("persist", false, "Persist eBooks in 'dir'. Default is to delete after sending.")
serverCmd.Flags().String("basepath", "/", `Base path where the application is accessible. For example "/openbooks/".`)
}

// TODO: Something is broken with this new setup...
var serverCmd = &cobra.Command{
Use: "server",
Short: "Run OpenBooks in server mode.",
Expand All @@ -36,6 +36,15 @@ var serverCmd = &cobra.Command{
port, _ := cmd.Flags().GetString("port")
dir, _ := cmd.Flags().GetString("dir")
persist, _ := cmd.Flags().GetBool("persist")
basepath, _ := cmd.Flags().GetString("basepath")

// If cli flag isn't set (default value) check for the presence of an
// environment variable and use it if found.
if basepath == cmd.Flag("basepath").DefValue {
if envPath, present := os.LookupEnv("BASE_PATH"); present {
basepath = envPath
}
}

config := server.Config{
Log: log,
Expand All @@ -44,6 +53,7 @@ var serverCmd = &cobra.Command{
Port: port,
DownloadDir: dir,
Persist: persist,
Basepath: basepath,
}

server.Start(config)
Expand Down
6 changes: 6 additions & 0 deletions docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

`docker run -d -p 8080:80 evanbuss/openbooks --name my_irc_name`

### Host at a sub path behind a reverse proxy

`docker run -d -p 8080:80 -e BASE_PATH=/openbooks/ evanbuss/openbooks`

## Arguments

```
Expand All @@ -38,6 +42,8 @@ services:
restart: unless-stopped
container_name: OpenBooks
command: --persist
environment:
- BASE_PATH=/openbooks/
image: evan-buss/openbooks:latest
volumes:
Expand Down
28 changes: 14 additions & 14 deletions server/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions server/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
},
"devDependencies": {
"@types/lodash": "^4.14.172",
"@types/node": "^16.6.2",
"@types/react": "^17.0.18",
"@types/node": "^16.7.1",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"@types/styled-components": "^5.1.12",
"@vitejs/plugin-react-refresh": "^1.3.6",
Expand Down
11 changes: 7 additions & 4 deletions server/app/src/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import serverReducer from "./serverSlice";
import stateReducer from "./stateSlice";
import { enableMapSet } from "immer";

const wsProtocol = window.location.protocol === "https:" ? "wss://" : "ws://";
const wsHost = import.meta.env.DEV ? "localhost:5228" : window.location.host;
const wsUrl = `${wsProtocol}${wsHost}/ws`;
const websocketURL = new URL(window.location.href + "ws")
if (websocketURL.protocol.startsWith("https")) {
websocketURL.protocol = websocketURL.protocol.replace("https", "wss");
} else {
websocketURL.protocol = websocketURL.protocol.replace("http", "ws");
}

enableMapSet();

Expand All @@ -18,7 +21,7 @@ export const store = configureStore({
history: historyReducer,
servers: serverReducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(websocketConn(wsUrl))
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(websocketConn(websocketURL.href))
});

const saveState = (key: string, state: any): void => {
Expand Down
3 changes: 2 additions & 1 deletion server/app/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import reactRefresh from '@vitejs/plugin-react-refresh'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [reactRefresh()]
plugins: [reactRefresh()],
base: "./"
})
47 changes: 47 additions & 0 deletions server/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package server

import (
"embed"
"fmt"
"io/fs"
"log"
"net/http"
"path"
)

//go:embed app/dist
var reactClient embed.FS

func registerRoutes(hub *Hub) {
basePath := getBaseRoute()
fmt.Printf("Base Path: %s\n", basePath)

http.Handle(basePath, serveStaticFiles(basePath, "app/dist"))

http.HandleFunc(path.Join(basePath, "ws"), func(w http.ResponseWriter, r *http.Request) {
serveWs(hub, w, r)
})

http.HandleFunc(path.Join(basePath, "connections"), func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "There are currently %d active connections.", *numConnections)
})
}

func serveStaticFiles(basePath, assetPath string) http.Handler {
// update the embedded file system's tree so that index.html is at the root
app, err := fs.Sub(reactClient, assetPath)
if err != nil {
log.Fatal(err)
}

// strip the predefined base path and serve the static file
return http.StripPrefix(basePath, http.FileServer(http.FS(app)))
}

func getBaseRoute() string {
cleaned := path.Clean(config.Basepath)
if cleaned == "/" {
return cleaned
}
return cleaned + "/"
}
39 changes: 6 additions & 33 deletions server/server.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package server

import (
"embed"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"syscall"
"time"
)

//go:embed app/dist
var reactClient embed.FS

// Config contains settings for server
type Config struct {
Log bool
Expand All @@ -23,6 +19,7 @@ type Config struct {
UserName string
Persist bool
DownloadDir string
Basepath string
}

var config Config
Expand All @@ -45,38 +42,14 @@ func Start(conf Config) {
os.Exit(1)
}()

http.Handle("/", AddRoutePrefix("/app/dist/", http.FileServer(http.FS(reactClient))))

http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(hub, w, r)
})

http.HandleFunc("/connections", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "There are currently %d active connections.", *numConnections)
})
registerRoutes(hub)

if config.OpenBrowser {
openbrowser("http://127.0.0.1:" + config.Port + "/")
browserUrl := "http://127.0.0.1:" + path.Join(config.Port+config.Basepath)
fmt.Println(browserUrl)
openbrowser(browserUrl)
}

log.Printf("OpenBooks is listening on port %v", config.Port)
log.Fatal(http.ListenAndServe(":"+config.Port, nil))
}

// AddRoutePrefix adds a prefix to the request.
func AddRoutePrefix(prefix string, h http.Handler) http.Handler {
if prefix == "" {
return h
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := prefix + r.URL.Path
rp := prefix + r.URL.RawPath
r2 := new(http.Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
r2.URL.RawPath = rp
h.ServeHTTP(w, r2)
})
}
4 changes: 3 additions & 1 deletion todo
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
- Server / Title - Search Text Box
- Format - Find unique formats and have clickable options
- Use new Pulsar component instead of custom version
- Make the "connected" indicator actually work...
- Make the "connected" indicator actually work...
- Show IRC name in web interface
- Show raw IRC logs in web interface

0 comments on commit 3c426e9

Please sign in to comment.