Skip to content

Commit

Permalink
fix #2002
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieuancelin committed Oct 2, 2024
1 parent 660da56 commit 524ce97
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 30 deletions.
27 changes: 11 additions & 16 deletions otoroshi/app/gateway/errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ object Errors {
404 -> ("The page you're looking for does not exist", "notFound.gif")
)

private val cache = Scaffeine().expireAfterWrite(60.seconds).maximumSize(100).build[String, Option[ErrorTemplate]]()

private def sendAnalytics(
headers: Seq[Header],
errorId: String,
Expand Down Expand Up @@ -371,7 +369,10 @@ object Errors {
emptyBody: Boolean,
errorId: String
)(implicit env: Env, ec: ExecutionContext): Future[Result] = {
env.datastores.errorTemplateDataStore.findById(descriptorId).map {
env.datastores.errorTemplateDataStore.findById(descriptorId).flatMap {
case Some(tmpl) => tmpl.some.vfuture
case None => env.datastores.errorTemplateDataStore.findById("global")
}.map {
case None => standardResult(req, status, message, maybeCauseId, emptyBody, false)
case Some(errorTemplate) => {
val accept = req.headers.get("Accept").getOrElse("text/html").split(",").toSeq
Expand Down Expand Up @@ -403,18 +404,7 @@ object Errors {
}

private def errorTemplate(descriptorId: String)(implicit env: Env, ec: ExecutionContext): Option[ErrorTemplate] = {
cache.getIfPresent(descriptorId) match {
case Some(opt) => opt
case None =>
env.proxyState.errorTemplate(descriptorId) match {
case None =>
cache.put(descriptorId, None)
None
case Some(errorTemplate) =>
cache.put(descriptorId, errorTemplate.some)
errorTemplate.some
}
}
env.proxyState.errorTemplate(descriptorId).orElse(env.proxyState.errorTemplate("global"))
}

private def customResultSync(
Expand Down Expand Up @@ -549,7 +539,12 @@ object Errors {
route.transformError(ctx)(env, ec, env.otoroshiMaterializer)
}
}
case _ => standardResult(req, status, message, maybeCauseId, emptyBody, false).vfuture
case _ =>
env.proxyState.errorTemplate("global") match {
case None => standardResult(req, status, message, maybeCauseId, emptyBody, false).vfuture
case Some(_) => customResult("global", req, status, message, maybeCauseId, emptyBody, errorId)
}

}) andThen {
case scala.util.Success(resp) if sendEvent =>
sendAnalytics(
Expand Down
19 changes: 13 additions & 6 deletions otoroshi/app/models/template.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package otoroshi.models

import otoroshi.env.Env
import otoroshi.storage.BasicStore
import otoroshi.utils.RegexPool
import otoroshi.utils.syntax.implicits._
import play.api.Logger
import play.api.libs.json._
Expand All @@ -21,15 +22,19 @@ case class ErrorTemplate(
template50x: String,
templateBuild: String,
templateMaintenance: String,
genericTemplates: Map[String, String],
messages: Map[String, String] = Map.empty[String, String]
) extends EntityLocationSupport {
def renderHtml(status: Int, causeId: String, otoroshiMessage: String, errorId: String): String = {
val template = (status, causeId) match {
case (_, "errors.service.in.maintenance") => templateMaintenance
case (_, "errors.service.under.construction") => templateBuild
case (s, _) if s > 399 && s < 500 => template40x
case (s, _) if s > 499 && s < 600 => template50x
case _ => template50x
val template = genericTemplates.get(causeId).orElse(genericTemplates.keys.find(k => RegexPool.apply(k).matches(causeId)).flatMap(k => genericTemplates.get(k))) match {
case Some(tmpl) => tmpl
case None => (status, causeId) match {
case (_, "errors.service.in.maintenance") => templateMaintenance
case (_, "errors.service.under.construction") => templateBuild
case (s, _) if s > 399 && s < 500 => template40x
case (s, _) if s > 499 && s < 600 => template50x
case _ => template50x
}
}
val messageKey = s"message-$status"
val message = messages.getOrElse(messageKey, otoroshiMessage)
Expand Down Expand Up @@ -77,6 +82,7 @@ object ErrorTemplate {
"template50x" -> o.template50x,
"templateBuild" -> o.templateBuild,
"templateMaintenance" -> o.templateMaintenance,
"genericTemplates" -> o.genericTemplates,
"messages" -> o.messages
)
override def reads(json: JsValue): JsResult[ErrorTemplate] = Try {
Expand All @@ -93,6 +99,7 @@ object ErrorTemplate {
template50x = json.select("template50x").asString,
templateBuild = json.select("templateBuild").asString,
templateMaintenance = json.select("templateMaintenance").asString,
genericTemplates = json.select("genericTemplates").asOpt[Map[String, String]].getOrElse(Map.empty),
messages = json.select("messages").asOpt[Map[String, String]].getOrElse(Map.empty)
)
)
Expand Down
4 changes: 2 additions & 2 deletions otoroshi/app/next/proxy/engine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ class ProxyEngine() extends RequestHandler {
FEither.left(
NgResultProxyEngineError(
otoroshiJsonError(
Json.obj("error" -> "service_unavailable", "error_description" -> "Service in maintenance mode"),
Json.obj("error" -> "errors.service.in.maintenance", "error_description" -> "Service in maintenance mode"),
Results.ServiceUnavailable,
attrs.get(otoroshi.next.plugins.Keys.RouteKey),
attrs,
Expand Down Expand Up @@ -1540,7 +1540,7 @@ class ProxyEngine() extends RequestHandler {
Json
.obj(
"error" -> "internal_server_error",
"error_description" -> "an error happened during before-request plugins phase"
"error_description" -> "an error happened during after-request plugins phase"
)
.applyOnIf(env.isDev) { obj =>
obj ++ Json.obj("jvm_error" -> JsonHelpers.errToJson(exception))
Expand Down
7 changes: 4 additions & 3 deletions otoroshi/javascript/src/components/inputs/ObjectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export class ObjectInput extends React.Component {
)}
<div className={`${props.ngOptions?.spread ? 'col-sm-12' : 'col-sm-10'}`}>
<div className="input-group justify-content-between">
{props.itemRenderer && props.itemRenderer(key, value, idx)}
{props.itemRenderer && props.itemRenderer(key, value, idx, e => this.changeKey(idx, key, e), e => this.changeValue(idx, key, e))}
{!props.itemRenderer && (
<>
<input
Expand All @@ -150,14 +150,15 @@ export class ObjectInput extends React.Component {
value={key}
onChange={(e) => this.changeKey(idx, key, e)}
/>
<input
{props.valueRenderer && props.valueRenderer(key, value, idx, e => this.changeValue(idx, key, e))}
{!props.valueRenderer && <input
disabled={props.disabled}
type="text"
className="form-control"
placeholder={props.placeholderValue}
value={value}
onChange={(e) => this.changeValue(idx, key, e)}
/>
/>}
</>
)}
<span className="input-group-btn">
Expand Down
56 changes: 54 additions & 2 deletions otoroshi/javascript/src/pages/ErrorTemplatePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { Component } from 'react';
import { Table, Form } from '../components/inputs';

import * as BackOfficeServices from '../services/BackOfficeServices';
import isFunction from "lodash/isFunction";
import AceEditor from "react-ace";

export class ErrorTemplatesPage extends Component {
state = { list: [], fetched: false };
Expand Down Expand Up @@ -153,6 +155,14 @@ export class ErrorTemplatesPage extends Component {
fields: ['id', 'name'],
}).then((routeCompositions) => {
const list = [
{
label: (
<div>
All routes and services
</div>
),
value: "global"
},
...services.data.map((s) => ({
label: (
<div>
Expand Down Expand Up @@ -201,6 +211,7 @@ export class ErrorTemplatesPage extends Component {
'template50x',
'templateBuild',
'templateMaintenance',
'genericTemplates',
'messages',
];

Expand Down Expand Up @@ -253,7 +264,48 @@ export class ErrorTemplatesPage extends Component {
placeholder: 'template for maintenance errors',
},
},
messages: { type: 'object', props: { label: 'messages' } },
genericTemplates: {
type: 'object',
props: {
label: 'Generic templates',
placeholderKey: 'template causeId',
itemRenderer: (key, value, idx, onKeyChange, onChange) => (
<div style={{ width: '100%', display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-start' }}>
<div style={{ width: '50%' }}>
<input
type="text"
className="form-control"
placeholder="Template causeId"
value={key}
onChange={onKeyChange}
style={{ marginRight: 2 }}
/>
</div>
<div style={{width: '50%'}}>
<AceEditor
theme={document.body.classList.contains('white-mode') ? 'xcode' : 'monokai'}
mode="html"
onChange={e => {
console.log(e)
onChange({ target: { value: e }})
}}
value={value || ''}
name="scriptParam"
editorProps={{$blockScrolling: true }}
height={'300px'}
width="100%"
showGutter={true}
highlightActiveLine={true}
tabSize={2}
enableBasicAutocompletion={true}
enableLiveAutocompletion={true}
/>
</div>
</div>
)
}
},
messages: {type: 'object', props: {label: 'messages'}},
});

render() {
Expand Down Expand Up @@ -290,7 +342,7 @@ export class ErrorTemplatesPage extends Component {
extractKey={(item) => {
return item.serviceId;
}}
itemUrl={(i) => `/bo/dashboard/error-templates/edit/${i.id}`}
itemUrl={(i) => `/bo/dashboard/error-templates/edit/${i.serviceId || i.id}`}
export={true}
kubernetesKind="ErrorTemplate"
/>
Expand Down
2 changes: 1 addition & 1 deletion otoroshi/javascript/src/services/BackOfficeServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,7 @@ export function createErrorTemplate(ak) {
}

export function updateErrorTemplate(ak) {
return fetch(`/bo/api/proxy/api/error-templates/${ak.id}`, {
return fetch(`/bo/api/proxy/api/error-templates/${ak.serviceId}`, {
method: 'PUT',
credentials: 'include',
headers: {
Expand Down

0 comments on commit 524ce97

Please sign in to comment.