78 404 when the application put under nginx subdirectory with proxy pass (#79)

* use http.FileServer insetead of custom middleware

* fixed behavior under reverse proxy

* enabled reverse proxy subfolder as "domain value"

* domain validation

* code refactoring

* code refactoring

* updated translation
This commit is contained in:
Marco
2023-08-21 12:24:50 +02:00
committed by GitHub
parent a005f159c6
commit c50c1f627e
8 changed files with 55 additions and 29 deletions

View File

@@ -34,6 +34,7 @@ languages:
clipboardAction: Copied URL to clipboard clipboardAction: Copied URL to clipboard
playlistCheckbox: Download playlist (it will take time, after submitting you may close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may close this window)
restartAppMessage: Needs a page reload to take effect restartAppMessage: Needs a page reload to take effect
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
italian: italian:
urlInput: URL di YouTube o di qualsiasi altro servizio supportato urlInput: URL di YouTube o di qualsiasi altro servizio supportato
statusTitle: Stato statusTitle: Stato
@@ -67,6 +68,7 @@ languages:
clipboardAction: URL copiato negli appunti clipboardAction: URL copiato negli appunti
playlistCheckbox: Download playlist (richiederà tempo, puoi chiudere la finestra dopo l'inoltro) playlistCheckbox: Download playlist (richiederà tempo, puoi chiudere la finestra dopo l'inoltro)
restartAppMessage: La finestra deve essere ricaricata perché abbia effetto restartAppMessage: La finestra deve essere ricaricata perché abbia effetto
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
chinese: chinese:
urlInput: YouTube 或其他受支持服务的视频网址 urlInput: YouTube 或其他受支持服务的视频网址
statusTitle: 状态 statusTitle: 状态
@@ -100,6 +102,7 @@ languages:
archiveTitle: 归档 archiveTitle: 归档
clipboardAction: 复制 URL 到剪贴板 clipboardAction: 复制 URL 到剪贴板
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
spanish: spanish:
urlInput: URL de YouTube u otro servicio compatible urlInput: URL de YouTube u otro servicio compatible
statusTitle: Estado statusTitle: Estado
@@ -132,6 +135,7 @@ languages:
archiveTitle: Archive archiveTitle: Archive
clipboardAction: Copied URL to clipboard clipboardAction: Copied URL to clipboard
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
russian: russian:
urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса
statusTitle: Статус statusTitle: Статус
@@ -164,6 +168,7 @@ languages:
archiveTitle: Архив archiveTitle: Архив
clipboardAction: URL скопирован в буфер обмена clipboardAction: URL скопирован в буфер обмена
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
korean: korean:
urlInput: YouTube나 다른 지원되는 사이트의 URL urlInput: YouTube나 다른 지원되는 사이트의 URL
statusTitle: 상태 statusTitle: 상태
@@ -196,6 +201,7 @@ languages:
archiveTitle: Archive archiveTitle: Archive
clipboardAction: Copied URL to clipboard clipboardAction: Copied URL to clipboard
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
japanese: japanese:
urlInput: YouTubeまたはサポート済み動画のURL urlInput: YouTubeまたはサポート済み動画のURL
statusTitle: 状態 statusTitle: 状態
@@ -229,6 +235,7 @@ languages:
archiveTitle: Archive archiveTitle: Archive
clipboardAction: Copied URL to clipboard clipboardAction: Copied URL to clipboard
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
catalan: catalan:
urlInput: URL de YouTube o d'un altre servei compatible urlInput: URL de YouTube o d'un altre servei compatible
statusTitle: Estat statusTitle: Estat
@@ -261,6 +268,7 @@ languages:
archiveTitle: Archive archiveTitle: Archive
clipboardAction: Copied URL to clipboard clipboardAction: Copied URL to clipboard
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
ukrainian: ukrainian:
urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу
statusTitle: Статус statusTitle: Статус
@@ -293,6 +301,7 @@ languages:
archiveTitle: Архів archiveTitle: Архів
clipboardAction: URL скопійовано в буфер обміну clipboardAction: URL скопійовано в буфер обміну
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder
polish: polish:
urlInput: Adres URL YouTube lub innej obsługiwanej usługi urlInput: Adres URL YouTube lub innej obsługiwanej usługi
statusTitle: Status statusTitle: Status
@@ -325,3 +334,4 @@ languages:
archiveTitle: Archiwum archiveTitle: Archiwum
clipboardAction: Adres URL zostanie skopiowany do schowka clipboardAction: Adres URL zostanie skopiowany do schowka
playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window) playlistCheckbox: Download playlist (it will take time, after submitting you may even close this window)
servedFromReverseProxyCheckbox: Is behind a reverse proxy subfolder

View File

@@ -1,18 +1,6 @@
import { atom, selector } from 'recoil' import { atom, selector } from 'recoil'
import { prefersDarkMode } from '../utils' import { prefersDarkMode } from '../utils'
export type Language =
| 'english'
| 'chinese'
| 'russian'
| 'italian'
| 'spanish'
| 'korean'
| 'japanese'
| 'catalan'
| 'ukrainian'
| 'polish'
export const languages = [ export const languages = [
'english', 'english',
'chinese', 'chinese',
@@ -26,6 +14,8 @@ export const languages = [
'polish', 'polish',
] as const ] as const
export type Language = (typeof languages)[number]
export type Theme = 'light' | 'dark' | 'system' export type Theme = 'light' | 'dark' | 'system'
export type ThemeNarrowed = 'light' | 'dark' export type ThemeNarrowed = 'light' | 'dark'
@@ -40,6 +30,7 @@ export interface SettingsState {
pathOverriding: boolean pathOverriding: boolean
enableCustomArgs: boolean enableCustomArgs: boolean
listView: boolean listView: boolean
servedFromReverseProxy: boolean
} }
export const languageState = atom<Language>({ export const languageState = atom<Language>({
@@ -133,9 +124,20 @@ export const listViewState = atom({
] ]
}) })
export const servedFromReverseProxyState = atom({
key: 'servedFromReverseProxyState',
default: localStorage.getItem('reverseProxy') === "true",
effects: [
({ onSet }) =>
onSet(a => localStorage.setItem('reverseProxy', a.toString()))
]
})
export const serverAddressAndPortState = selector({ export const serverAddressAndPortState = selector({
key: 'serverAddressAndPortState', key: 'serverAddressAndPortState',
get: ({ get }) => `${get(serverAddressState)}:${get(serverPortState)}` get: ({ get }) => get(servedFromReverseProxyState)
? `${get(serverAddressState)}`
: `${get(serverAddressState)}:${get(serverPortState)}`
}) })
export const serverURL = selector({ export const serverURL = selector({
@@ -184,5 +186,6 @@ export const settingsState = selector<SettingsState>({
pathOverriding: get(pathOverridingState), pathOverriding: get(pathOverridingState),
enableCustomArgs: get(enableCustomArgsState), enableCustomArgs: get(enableCustomArgsState),
listView: get(listViewState), listView: get(listViewState),
servedFromReverseProxy: get(servedFromReverseProxyState),
}) })
}) })

View File

@@ -1,6 +1,6 @@
import { CircularProgress } from '@mui/material' import { CircularProgress } from '@mui/material'
import { Suspense, lazy } from 'react' import { Suspense, lazy } from 'react'
import { createBrowserRouter } from 'react-router-dom' import { createHashRouter } from 'react-router-dom'
import Layout from './Layout' import Layout from './Layout'
const Home = lazy(() => import('./views/Home')) const Home = lazy(() => import('./views/Home'))
@@ -10,7 +10,7 @@ const Settings = lazy(() => import('./views/Settings'))
const ErrorBoundary = lazy(() => import('./components/ErrorBoundary')) const ErrorBoundary = lazy(() => import('./components/ErrorBoundary'))
export const router = createBrowserRouter([ export const router = createHashRouter([
{ {
path: '/', path: '/',
Component: () => <Layout />, Component: () => <Layout />,

View File

@@ -19,9 +19,13 @@ export function validateIP(ipAddr: string): boolean {
* @param domainName * @param domainName
* @returns domain validity test * @returns domain validity test
*/ */
export function validateDomain(domainName: string): boolean { export function validateDomain(url: string): boolean {
let domainRegex = /[^@ \t\r\n]+.[^@ \t\r\n]+\.[^@ \t\r\n]+/ const urlRegex = /(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
return domainRegex.test(domainName) || domainName === 'localhost' const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
const [name, slug] = url.split('/')
return urlRegex.test(url) || name === 'localhost' && slugRegex.test(slug)
} }
/** /**

View File

@@ -1,5 +1,6 @@
import { import {
Button, Button,
Checkbox,
Container, Container,
FormControl, FormControl,
FormControlLabel, FormControlLabel,
@@ -36,6 +37,7 @@ import {
languages, languages,
latestCliArgumentsState, latestCliArgumentsState,
pathOverridingState, pathOverridingState,
servedFromReverseProxyState,
serverAddressState, serverAddressState,
serverPortState, serverPortState,
themeState themeState
@@ -48,6 +50,7 @@ import { validateDomain, validateIP } from '../utils'
// NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS // NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS
export default function Settings() { export default function Settings() {
const [reverseProxy, setReverseProxy] = useRecoilState(servedFromReverseProxyState)
const [formatSelection, setFormatSelection] = useRecoilState(formatSelectionState) const [formatSelection, setFormatSelection] = useRecoilState(formatSelectionState)
const [pathOverriding, setPathOverriding] = useRecoilState(pathOverridingState) const [pathOverriding, setPathOverriding] = useRecoilState(pathOverridingState)
const [fileRenaming, setFileRenaming] = useRecoilState(fileRenamingState) const [fileRenaming, setFileRenaming] = useRecoilState(fileRenamingState)
@@ -154,16 +157,27 @@ export default function Settings() {
InputProps={{ InputProps={{
startAdornment: <InputAdornment position="start">ws://</InputAdornment>, startAdornment: <InputAdornment position="start">ws://</InputAdornment>,
}} }}
sx={{ mb: 2 }}
/> />
</Grid> </Grid>
<Grid item xs={12} md={1}> <Grid item xs={12} md={1}>
<TextField <TextField
disabled={reverseProxy}
fullWidth fullWidth
label={i18n.t('serverPortTitle')} label={i18n.t('serverPortTitle')}
defaultValue={serverPort} defaultValue={serverPort}
onChange={(e) => serverPort$.next(e.currentTarget.value)} onChange={(e) => serverPort$.next(e.currentTarget.value)}
error={isNaN(Number(serverPort)) || Number(serverPort) > 65535} error={isNaN(Number(serverPort)) || Number(serverPort) > 65535}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={
<Checkbox
defaultChecked={reverseProxy}
onChange={() => setReverseProxy(state => !state)}
/>
}
label={i18n.t('servedFromReverseProxyCheckbox')}
sx={{ mb: 2 }} sx={{ mb: 2 }}
/> />
</Grid> </Grid>

View File

@@ -1,7 +1,6 @@
import react from '@vitejs/plugin-react-swc' import react from '@vitejs/plugin-react-swc'
import ViteYaml from '@modyfi/vite-plugin-yaml' import ViteYaml from '@modyfi/vite-plugin-yaml'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { resolve } from 'path'
export default defineConfig(() => { export default defineConfig(() => {
return { return {
@@ -9,10 +8,9 @@ export default defineConfig(() => {
react(), react(),
ViteYaml(), ViteYaml(),
], ],
root: resolve(__dirname, '.'), base: '',
build: { build: {
emptyOutDir: true, emptyOutDir: true,
outDir: resolve(__dirname, 'dist'),
} }
} }
}) })

View File

@@ -22,7 +22,8 @@ var (
requireAuth bool requireAuth bool
rpcSecret string rpcSecret string
//go:embed frontend/dist //go:embed frontend/dist/index.html
//go:embed frontend/dist/assets/*
frontend embed.FS frontend embed.FS
) )

View File

@@ -43,7 +43,6 @@ func RunBlocking(port int, frontend fs.FS) {
mq: mq, mq: mq,
}) })
// http-post
go gracefulShutdown(srv, &db) go gracefulShutdown(srv, &db)
go autoPersist(time.Minute*5, &db) go autoPersist(time.Minute*5, &db)
@@ -59,12 +58,9 @@ func newServer(c serverConfig) *http.Server {
r.Use(cors.AllowAll().Handler) r.Use(cors.AllowAll().Handler)
r.Use(middleware.Logger) r.Use(middleware.Logger)
sh := middlewares.NewSpaHandler("index.html", c.frontend) app := http.FileServer(http.FS(c.frontend))
sh.AddClientRoute("/settings")
sh.AddClientRoute("/archive")
sh.AddClientRoute("/login")
r.Get("/*", sh.Handler()) r.Mount("/", app)
// Archive routes // Archive routes
r.Route("/archive", func(r chi.Router) { r.Route("/archive", func(r chi.Router) {