Support reverse proxy with path rewrite (#127)
This commit is contained in:
parent
347f3ce39e
commit
ec5b1f8c30
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gotify/location"
|
|
||||||
"github.com/gotify/server/auth"
|
"github.com/gotify/server/auth"
|
||||||
"github.com/gotify/server/model"
|
"github.com/gotify/server/model"
|
||||||
"github.com/h2non/filetype"
|
"github.com/h2non/filetype"
|
||||||
|
|
@ -70,7 +69,7 @@ func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) {
|
||||||
app.UserID = auth.GetUserID(ctx)
|
app.UserID = auth.GetUserID(ctx)
|
||||||
app.Internal = false
|
app.Internal = false
|
||||||
a.DB.CreateApplication(&app)
|
a.DB.CreateApplication(&app)
|
||||||
ctx.JSON(200, withAbsoluteURL(ctx, &app))
|
ctx.JSON(200, withResolvedImage(&app))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +101,7 @@ func (a *ApplicationAPI) GetApplications(ctx *gin.Context) {
|
||||||
userID := auth.GetUserID(ctx)
|
userID := auth.GetUserID(ctx)
|
||||||
apps := a.DB.GetApplicationsByUser(userID)
|
apps := a.DB.GetApplicationsByUser(userID)
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
withAbsoluteURL(ctx, app)
|
withResolvedImage(app)
|
||||||
}
|
}
|
||||||
ctx.JSON(200, apps)
|
ctx.JSON(200, apps)
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +209,7 @@ func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) {
|
||||||
|
|
||||||
a.DB.UpdateApplication(app)
|
a.DB.UpdateApplication(app)
|
||||||
|
|
||||||
ctx.JSON(200, withAbsoluteURL(ctx, app))
|
ctx.JSON(200, withResolvedImage(app))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.AbortWithError(404, fmt.Errorf("app with id %d doesn't exists", id))
|
ctx.AbortWithError(404, fmt.Errorf("app with id %d doesn't exists", id))
|
||||||
|
|
@ -302,13 +301,22 @@ func (a *ApplicationAPI) UploadApplicationImage(ctx *gin.Context) {
|
||||||
|
|
||||||
app.Image = name + ext
|
app.Image = name + ext
|
||||||
a.DB.UpdateApplication(app)
|
a.DB.UpdateApplication(app)
|
||||||
ctx.JSON(200, withAbsoluteURL(ctx, app))
|
ctx.JSON(200, withResolvedImage(app))
|
||||||
} else {
|
} else {
|
||||||
ctx.AbortWithError(404, fmt.Errorf("client with id %d doesn't exists", id))
|
ctx.AbortWithError(404, fmt.Errorf("client with id %d doesn't exists", id))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withResolvedImage(app *model.Application) *model.Application {
|
||||||
|
if app.Image == "" {
|
||||||
|
app.Image = "static/defaultapp.png"
|
||||||
|
} else {
|
||||||
|
app.Image = "image/" + app.Image
|
||||||
|
}
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ApplicationAPI) applicationExists(token string) bool {
|
func (a *ApplicationAPI) applicationExists(token string) bool {
|
||||||
return a.DB.GetApplicationByToken(token) != nil
|
return a.DB.GetApplicationByToken(token) != nil
|
||||||
}
|
}
|
||||||
|
|
@ -319,15 +327,3 @@ func exist(path string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func withAbsoluteURL(ctx *gin.Context, app *model.Application) *model.Application {
|
|
||||||
url := location.Get(ctx)
|
|
||||||
|
|
||||||
if app.Image == "" {
|
|
||||||
url.Path = "static/defaultapp.png"
|
|
||||||
} else {
|
|
||||||
url.Path = "image/" + app.Image
|
|
||||||
}
|
|
||||||
app.Image = url.String()
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ func (s *ApplicationSuite) Test_CreateApplication_returnsApplicationWithID() {
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Token: firstApplicationToken,
|
Token: firstApplicationToken,
|
||||||
Name: "custom_name",
|
Name: "custom_name",
|
||||||
Image: "http://example.com/static/defaultapp.png",
|
Image: "static/defaultapp.png",
|
||||||
UserID: 5,
|
UserID: 5,
|
||||||
}
|
}
|
||||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
|
@ -162,8 +162,8 @@ func (s *ApplicationSuite) Test_GetApplications() {
|
||||||
s.a.GetApplications(s.ctx)
|
s.a.GetApplications(s.ctx)
|
||||||
|
|
||||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
first.Image = "http://example.com/static/defaultapp.png"
|
first.Image = "static/defaultapp.png"
|
||||||
second.Image = "http://example.com/static/defaultapp.png"
|
second.Image = "static/defaultapp.png"
|
||||||
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,8 +180,8 @@ func (s *ApplicationSuite) Test_GetApplications_WithImage() {
|
||||||
s.a.GetApplications(s.ctx)
|
s.a.GetApplications(s.ctx)
|
||||||
|
|
||||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
first.Image = "http://example.com/image/abcd.jpg"
|
first.Image = "image/abcd.jpg"
|
||||||
second.Image = "http://example.com/static/defaultapp.png"
|
second.Image = "static/defaultapp.png"
|
||||||
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1804,7 +1804,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Image",
|
"x-go-name": "Image",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"example": "https://example.com/image.jpeg"
|
"example": "image/image.jpeg"
|
||||||
},
|
},
|
||||||
"internal": {
|
"internal": {
|
||||||
"description": "Whether the application is an internal application. Internal applications should not be deleted.",
|
"description": "Whether the application is an internal application. Internal applications should not be deleted.",
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,14 @@ import (
|
||||||
|
|
||||||
// Serve serves the documentation.
|
// Serve serves the documentation.
|
||||||
func Serve(ctx *gin.Context) {
|
func Serve(ctx *gin.Context) {
|
||||||
url := location.Get(ctx)
|
base := location.Get(ctx).Host
|
||||||
ctx.Writer.WriteString(get(url.Host))
|
if basePathFromQuery := ctx.Query("base"); basePathFromQuery != "" {
|
||||||
|
base = basePathFromQuery
|
||||||
|
}
|
||||||
|
ctx.Writer.WriteString(get(base))
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(host string) string {
|
func get(base string) string {
|
||||||
box := packr.NewBox("./")
|
box := packr.NewBox("./")
|
||||||
return strings.Replace(box.String("spec.json"), "localhost", host, 1)
|
return strings.Replace(box.String("spec.json"), "localhost", base, 1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@ func TestServe(t *testing.T) {
|
||||||
ctx, _ := gin.CreateTestContext(recorder)
|
ctx, _ := gin.CreateTestContext(recorder)
|
||||||
withURL(ctx, "http", "example.com")
|
withURL(ctx, "http", "example.com")
|
||||||
|
|
||||||
ctx.Request = httptest.NewRequest("GET", "/swagger", nil)
|
ctx.Request = httptest.NewRequest("GET", "/swagger?base="+url.QueryEscape("127.0.0.1/proxy/"), nil)
|
||||||
|
|
||||||
Serve(ctx)
|
Serve(ctx)
|
||||||
|
|
||||||
content := recorder.Body.String()
|
content := recorder.Body.String()
|
||||||
assert.NotEmpty(t, content)
|
assert.NotEmpty(t, content)
|
||||||
|
assert.Contains(t, content, "127.0.0.1/proxy/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func withURL(ctx *gin.Context, scheme, host string) {
|
func withURL(ctx *gin.Context, scheme, host string) {
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,15 @@ var ui = `
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.20.5/swagger-ui-bundle.js"> </script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.20.5/swagger-ui-bundle.js"> </script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.20.5/swagger-ui-standalone-preset.js"> </script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.20.5/swagger-ui-standalone-preset.js"> </script>
|
||||||
<script>
|
<script>
|
||||||
|
function getBaseURL() {
|
||||||
|
var path = window.location.pathname
|
||||||
|
path = path.substr(0, path.lastIndexOf('/')+1)
|
||||||
|
return window.location.host + path;
|
||||||
|
}
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Begin Swagger UI call region
|
// Begin Swagger UI call region
|
||||||
const ui = SwaggerUIBundle({
|
const ui = SwaggerUIBundle({
|
||||||
url: "../swagger",
|
url: "swagger?base="+encodeURIComponent(getBaseURL()),
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
presets: [
|
presets: [
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ type Application struct {
|
||||||
//
|
//
|
||||||
// read only: true
|
// read only: true
|
||||||
// required: true
|
// required: true
|
||||||
// example: https://example.com/image.jpeg
|
// example: image/image.jpeg
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
Messages []MessageExternal `json:"-"`
|
Messages []MessageExternal `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "gotify-ui",
|
"name": "gotify-ui",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"homepage": ".",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^1.5.1",
|
"@material-ui/core": "^1.5.1",
|
||||||
"@material-ui/icons": "^2.0.3",
|
"@material-ui/icons": "^2.0.3",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import AddApplicationDialog from './AddApplicationDialog';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {observable} from 'mobx';
|
import {observable} from 'mobx';
|
||||||
import {inject, Stores} from '../inject';
|
import {inject, Stores} from '../inject';
|
||||||
|
import * as config from '../config';
|
||||||
import UpdateDialog from './UpdateApplicationDialog';
|
import UpdateDialog from './UpdateApplicationDialog';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
|
@ -152,7 +153,7 @@ const Row: SFC<IRowProps> = observer(
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell padding="checkbox">
|
<TableCell padding="checkbox">
|
||||||
<div style={{display: 'flex'}}>
|
<div style={{display: 'flex'}}>
|
||||||
<Avatar src={image} />
|
<Avatar src={config.get('url') + image} />
|
||||||
<IconButton onClick={fUpload} style={{height: 40}}>
|
<IconButton onClick={fUpload} style={{height: 40}}>
|
||||||
<CloudUpload />
|
<CloudUpload />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@ const defaultDevConfig = {
|
||||||
url: 'http://localhost:80/',
|
url: 'http://localhost:80/',
|
||||||
};
|
};
|
||||||
|
|
||||||
const {port, hostname, protocol} = window.location;
|
const {port, hostname, protocol, pathname} = window.location;
|
||||||
const slashes = protocol.concat('//');
|
const slashes = protocol.concat('//');
|
||||||
const url = slashes.concat(port ? hostname.concat(':', port) : hostname);
|
const path = pathname.endsWith('/') ? pathname : pathname.substring(0, pathname.lastIndexOf('/'));
|
||||||
|
const url = slashes.concat(port ? hostname.concat(':', port) : hostname) + path;
|
||||||
const urlWithSlash = url.endsWith('/') ? url : url.concat('/');
|
const urlWithSlash = url.endsWith('/') ? url : url.concat('/');
|
||||||
|
|
||||||
const defaultProdConfig = {
|
const defaultProdConfig = {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import Delete from '@material-ui/icons/Delete';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TimeAgo from 'react-timeago';
|
import TimeAgo from 'react-timeago';
|
||||||
import Container from '../common/Container';
|
import Container from '../common/Container';
|
||||||
|
import * as config from '../config';
|
||||||
import {StyleRulesCallback} from '@material-ui/core/styles/withStyles';
|
import {StyleRulesCallback} from '@material-ui/core/styles/withStyles';
|
||||||
|
|
||||||
const styles: StyleRulesCallback = () => ({
|
const styles: StyleRulesCallback = () => ({
|
||||||
|
|
@ -57,7 +58,7 @@ class Message extends React.PureComponent<IProps & WithStyles<typeof styles>> {
|
||||||
<Container style={{display: 'flex'}}>
|
<Container style={{display: 'flex'}}>
|
||||||
<div className={classes.imageWrapper}>
|
<div className={classes.imageWrapper}>
|
||||||
<img
|
<img
|
||||||
src={image}
|
src={config.get('url') + image}
|
||||||
alt="app logo"
|
alt="app logo"
|
||||||
width="70"
|
width="70"
|
||||||
height="70"
|
height="70"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue