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