Support reverse proxy with path rewrite (#127)

This commit is contained in:
饺子w 2019-02-14 01:47:48 +08:00 committed by Jannis Mattheis
parent 347f3ce39e
commit ec5b1f8c30
11 changed files with 43 additions and 34 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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.",

View File

@ -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)
}

View File

@ -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) {

View File

@ -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: [

View File

@ -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:"-"`
}

View File

@ -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",

View File

@ -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>

View File

@ -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 = {

View File

@ -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"