-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-15487] [Web UI] Spark Master UI to reverse proxy Application and Workers UI #13950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fe88e75
c695f3d
f435e4d
f7cec6c
f649e5d
128c4a6
0fd4477
9f6862e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,8 @@ import scala.collection.mutable.ArrayBuffer | |
| import scala.language.implicitConversions | ||
| import scala.xml.Node | ||
|
|
||
| import org.eclipse.jetty.client.api.Response | ||
| import org.eclipse.jetty.proxy.ProxyServlet | ||
| import org.eclipse.jetty.server.{Request, Server, ServerConnector} | ||
| import org.eclipse.jetty.server.handler._ | ||
| import org.eclipse.jetty.servlet._ | ||
|
|
@@ -186,6 +188,47 @@ private[spark] object JettyUtils extends Logging { | |
| contextHandler | ||
| } | ||
|
|
||
| /** Create a handler for proxying request to Workers and Application Drivers */ | ||
| def createProxyHandler( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Always a little scary to see string manipulation code with no tests.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might have overlooked the tests for other handlers, can you point me to them if there is any. I can add test for this handler too then. Of course I can break the string manipulation part in separate function and add test for that. let me know which is preferable.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 to targeted tests of string manipulation code. Move the string manipulation part into its own method, and write targeted unit tests for it.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a test under UISuite, let me know if that is not the right place for it. |
||
| prefix: String, | ||
| target: String): ServletContextHandler = { | ||
| val servlet = new ProxyServlet { | ||
| override def rewriteTarget(request: HttpServletRequest): String = { | ||
| val rewrittenURI = createProxyURI( | ||
| prefix, target, request.getRequestURI(), request.getQueryString()) | ||
| if (rewrittenURI == null) { | ||
| return null | ||
| } | ||
| if (!validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort())) { | ||
| return null | ||
| } | ||
| rewrittenURI.toString() | ||
| } | ||
|
|
||
| override def filterServerResponseHeader( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add empty line between methods |
||
| clientRequest: HttpServletRequest, | ||
| serverResponse: Response, | ||
| headerName: String, | ||
| headerValue: String): String = { | ||
| if (headerName.equalsIgnoreCase("location")) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would also be nice to have targeted unit tests for this code. |
||
| val newHeader = createProxyLocationHeader( | ||
| prefix, headerValue, clientRequest, serverResponse.getRequest().getURI()) | ||
| if (newHeader != null) { | ||
| return newHeader | ||
| } | ||
| } | ||
| super.filterServerResponseHeader( | ||
| clientRequest, serverResponse, headerName, headerValue) | ||
| } | ||
| } | ||
|
|
||
| val contextHandler = new ServletContextHandler | ||
| val holder = new ServletHolder(servlet) | ||
| contextHandler.setContextPath(prefix) | ||
| contextHandler.addServlet(holder, "/") | ||
| contextHandler | ||
| } | ||
|
|
||
| /** Add filters, if any, to the given list of ServletContextHandlers */ | ||
| def addFilters(handlers: Seq[ServletContextHandler], conf: SparkConf) { | ||
| val filters: Array[String] = conf.get("spark.ui.filters", "").split(',').map(_.trim()) | ||
|
|
@@ -332,6 +375,48 @@ private[spark] object JettyUtils extends Logging { | |
| redirectHandler | ||
| } | ||
|
|
||
| def createProxyURI(prefix: String, target: String, path: String, query: String): URI = { | ||
| if (!path.startsWith(prefix)) { | ||
| return null | ||
| } | ||
|
|
||
| val uri = new StringBuilder(target) | ||
| val rest = path.substring(prefix.length()) | ||
|
|
||
| if (!rest.isEmpty()) { | ||
| if (!rest.startsWith("/")) { | ||
| uri.append("/") | ||
| } | ||
| uri.append(rest) | ||
| } | ||
|
|
||
| val rewrittenURI = URI.create(uri.toString()) | ||
| if (query != null) { | ||
| return new URI( | ||
| rewrittenURI.getScheme(), | ||
| rewrittenURI.getAuthority(), | ||
| rewrittenURI.getPath(), | ||
| query, | ||
| rewrittenURI.getFragment() | ||
| ).normalize() | ||
| } | ||
| rewrittenURI.normalize() | ||
| } | ||
|
|
||
| def createProxyLocationHeader( | ||
| prefix: String, | ||
| headerValue: String, | ||
| clientRequest: HttpServletRequest, | ||
| targetUri: URI): String = { | ||
| val toReplace = targetUri.getScheme() + "://" + targetUri.getAuthority() | ||
| if (headerValue.startsWith(toReplace)) { | ||
| clientRequest.getScheme() + "://" + clientRequest.getHeader("host") + | ||
| prefix + headerValue.substring(toReplace.length()) | ||
| } else { | ||
| null | ||
| } | ||
| } | ||
|
|
||
| // Create a new URI from the arguments, handling IPv6 host encoding and default ports. | ||
| private def createRedirectURI( | ||
| scheme: String, server: String, port: Int, path: String, query: String) = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -510,4 +510,16 @@ private[spark] object UIUtils extends Logging { | |
|
|
||
| def getTimeZoneOffset() : Int = | ||
| TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 1000 / 60 | ||
|
|
||
| /** | ||
| * Return the correct Href after checking if master is running in the | ||
| * reverse proxy mode or not. | ||
| */ | ||
| def makeHref(proxy: Boolean, id: String, origHref: String): String = { | ||
| if (proxy) { | ||
|
||
| s"/proxy/$id" | ||
| } else { | ||
| origHref | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These new dependencies need to be added to the
copy-dependenciesinvocation later in this file.