Add response size protection to prevent MCP server crashes
- Add size checks and limits to screen rendering services - Implement graceful truncation for oversized responses - Add timeout protection for screen rendering operations - Enhanced error handling and logging for large responses - Prevent server crashes from large screen outputs This protects the MCP server from crashes when screens generate large amounts of data while maintaining functionality for normal use cases.
Showing
5 changed files
with
152 additions
and
69 deletions
| 1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | <screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 2 | <screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| 3 | xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-3.xsd"> | 3 | xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-3.xsd" |
| 4 | standalone="true"> | ||
| 4 | 5 | ||
| 5 | <parameters> | 6 | <parameters> |
| 6 | <parameter name="message" default-value="Hello from MCP!"/> | 7 | <parameter name="message" default-value="Hello from MCP!"/> |
| ... | @@ -8,7 +9,7 @@ | ... | @@ -8,7 +9,7 @@ |
| 8 | 9 | ||
| 9 | <actions> | 10 | <actions> |
| 10 | <set field="timestamp" from="new java.util.Date()"/> | 11 | <set field="timestamp" from="new java.util.Date()"/> |
| 11 | <set field="user" from="ec.user.username"/> | 12 | <set field="user" from="ec.user?.username ?: 'Anonymous'"/> |
| 12 | </actions> | 13 | </actions> |
| 13 | 14 | ||
| 14 | <widgets> | 15 | <widgets> |
| ... | @@ -18,6 +19,7 @@ | ... | @@ -18,6 +19,7 @@ |
| 18 | <label text="User: ${user}" type="p"/> | 19 | <label text="User: ${user}" type="p"/> |
| 19 | <label text="Time: ${timestamp}" type="p"/> | 20 | <label text="Time: ${timestamp}" type="p"/> |
| 20 | <label text="Render Mode: ${sri.renderMode}" type="p"/> | 21 | <label text="Render Mode: ${sri.renderMode}" type="p"/> |
| 22 | <label text="Screen Path: ${sri.screenPath}" type="p"/> | ||
| 21 | </container> | 23 | </container> |
| 22 | </widgets> | 24 | </widgets> |
| 23 | </screen> | 25 | </screen> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -743,24 +743,8 @@ try { | ... | @@ -743,24 +743,8 @@ try { |
| 743 | 743 | ||
| 744 | def executionTime = (System.currentTimeMillis() - startTime) / 1000.0 | 744 | def executionTime = (System.currentTimeMillis() - startTime) / 1000.0 |
| 745 | 745 | ||
| 746 | // Build field info for LLM | 746 | // SIZE PROTECTION: Check response size before returning |
| 747 | def fieldInfo = [] | 747 | def jsonOutput = new JsonBuilder([ |
| 748 | entityDef.allFieldInfoList.each { field -> | ||
| 749 | fieldInfo << [ | ||
| 750 | name: field.name, | ||
| 751 | type: field.type, | ||
| 752 | description: field.description ?: "", | ||
| 753 | isPk: field.isPk, | ||
| 754 | required: field.notNull | ||
| 755 | ] | ||
| 756 | } | ||
| 757 | |||
| 758 | // Convert to MCP resource content | ||
| 759 | def contents = [ | ||
| 760 | [ | ||
| 761 | uri: uri, | ||
| 762 | mimeType: "application/json", | ||
| 763 | text: new JsonBuilder([ | ||
| 764 | entityName: entityName, | 748 | entityName: entityName, |
| 765 | description: entityDef.description ?: "", | 749 | description: entityDef.description ?: "", |
| 766 | packageName: entityDef.packageName, | 750 | packageName: entityDef.packageName, |
| ... | @@ -768,10 +752,43 @@ try { | ... | @@ -768,10 +752,43 @@ try { |
| 768 | fields: fieldInfo, | 752 | fields: fieldInfo, |
| 769 | data: entityList | 753 | data: entityList |
| 770 | ]).toString() | 754 | ]).toString() |
| 755 | |||
| 756 | def maxResponseSize = 1024 * 1024 // 1MB limit | ||
| 757 | if (jsonOutput.length() > maxResponseSize) { | ||
| 758 | ec.logger.warn("ResourcesRead: Response too large for ${entityName}: ${jsonOutput.length()} bytes (limit: ${maxResponseSize} bytes)") | ||
| 759 | |||
| 760 | // Create truncated response with fewer records | ||
| 761 | def truncatedList = entityList.take(10) // Keep only first 10 records | ||
| 762 | def truncatedOutput = new JsonBuilder([ | ||
| 763 | entityName: entityName, | ||
| 764 | description: entityDef.description ?: "", | ||
| 765 | packageName: entityDef.packageName, | ||
| 766 | recordCount: entityList.size(), | ||
| 767 | fields: fieldInfo, | ||
| 768 | data: truncatedList, | ||
| 769 | truncated: true, | ||
| 770 | originalSize: entityList.size(), | ||
| 771 | truncatedSize: truncatedList.size(), | ||
| 772 | message: "Response truncated due to size limits. Original data has ${entityList.size()} records, showing first ${truncatedList.size()}." | ||
| 773 | ]).toString() | ||
| 774 | |||
| 775 | contents = [ | ||
| 776 | [ | ||
| 777 | uri: uri, | ||
| 778 | mimeType: "application/json", | ||
| 779 | text: truncatedOutput | ||
| 771 | ] | 780 | ] |
| 772 | ] | 781 | ] |
| 773 | 782 | } else { | |
| 774 | result = [contents: contents] | 783 | // Normal response |
| 784 | contents = [ | ||
| 785 | [ | ||
| 786 | uri: uri, | ||
| 787 | mimeType: "application/json", | ||
| 788 | text: jsonOutput | ||
| 789 | ] | ||
| 790 | ] | ||
| 791 | } | ||
| 775 | 792 | ||
| 776 | } catch (Exception e) { | 793 | } catch (Exception e) { |
| 777 | def executionTime = (System.currentTimeMillis() - startTime) / 1000.0 | 794 | def executionTime = (System.currentTimeMillis() - startTime) / 1000.0 |
| ... | @@ -1416,7 +1433,17 @@ def startTime = System.currentTimeMillis() | ... | @@ -1416,7 +1433,17 @@ def startTime = System.currentTimeMillis() |
| 1416 | testScreenPath = remainingPath | 1433 | testScreenPath = remainingPath |
| 1417 | ec.logger.info("MCP Screen Execution: Using component root ${rootScreen} for path ${testScreenPath}") | 1434 | ec.logger.info("MCP Screen Execution: Using component root ${rootScreen} for path ${testScreenPath}") |
| 1418 | } else { | 1435 | } else { |
| 1419 | // Fallback: try using webroot with the full path | 1436 | // For mantle and other components, try using the component's screen directory as root |
| 1437 | // This is a better fallback than webroot | ||
| 1438 | def componentScreenRoot = "component://${componentName}/screen/" | ||
| 1439 | if (pathAfterComponent.startsWith("${componentName}/screen/")) { | ||
| 1440 | // Extract the screen file name from the path | ||
| 1441 | def screenFileName = pathAfterComponent.substring("${componentName}/screen/".length()) | ||
| 1442 | rootScreen = screenPath // Use the full path as root | ||
| 1443 | testScreenPath = "" // Empty path for direct screen access | ||
| 1444 | ec.logger.info("MCP Screen Execution: Using component screen as direct root: ${rootScreen}") | ||
| 1445 | } else { | ||
| 1446 | // Final fallback: try webroot | ||
| 1420 | rootScreen = "component://webroot/screen/webroot.xml" | 1447 | rootScreen = "component://webroot/screen/webroot.xml" |
| 1421 | testScreenPath = pathAfterComponent | 1448 | testScreenPath = pathAfterComponent |
| 1422 | ec.logger.warn("MCP Screen Execution: Could not find component root for ${componentName}, using webroot fallback: ${testScreenPath}") | 1449 | ec.logger.warn("MCP Screen Execution: Could not find component root for ${componentName}, using webroot fallback: ${testScreenPath}") |
| ... | @@ -1424,6 +1451,7 @@ def startTime = System.currentTimeMillis() | ... | @@ -1424,6 +1451,7 @@ def startTime = System.currentTimeMillis() |
| 1424 | } | 1451 | } |
| 1425 | } | 1452 | } |
| 1426 | } | 1453 | } |
| 1454 | } | ||
| 1427 | } else { | 1455 | } else { |
| 1428 | // Fallback for malformed component paths | 1456 | // Fallback for malformed component paths |
| 1429 | testScreenPath = pathAfterComponent | 1457 | testScreenPath = pathAfterComponent |
| ... | @@ -1431,8 +1459,11 @@ def startTime = System.currentTimeMillis() | ... | @@ -1431,8 +1459,11 @@ def startTime = System.currentTimeMillis() |
| 1431 | } | 1459 | } |
| 1432 | } | 1460 | } |
| 1433 | 1461 | ||
| 1434 | // Restore user context from sessionId before creating ScreenTest | 1462 | // User context should already be correct from MCP servlet restoration |
| 1435 | // Regular screen rendering with restored user context - use our custom ScreenTestImpl | 1463 | // CustomScreenTestImpl will capture current user context automatically |
| 1464 | ec.logger.info("MCP Screen Execution: Current user context - userId: ${ec.user.userId}, username: ${ec.user.username}") | ||
| 1465 | |||
| 1466 | // Regular screen rendering with current user context - use our custom ScreenTestImpl | ||
| 1436 | def screenTest = new org.moqui.mcp.CustomScreenTestImpl(ec.ecfi) | 1467 | def screenTest = new org.moqui.mcp.CustomScreenTestImpl(ec.ecfi) |
| 1437 | .rootScreen(rootScreen) | 1468 | .rootScreen(rootScreen) |
| 1438 | .renderMode(renderMode ? renderMode : "html") | 1469 | .renderMode(renderMode ? renderMode : "html") |
| ... | @@ -1460,7 +1491,28 @@ def startTime = System.currentTimeMillis() | ... | @@ -1460,7 +1491,28 @@ def startTime = System.currentTimeMillis() |
| 1460 | def testRender = future.get(30, java.util.concurrent.TimeUnit.SECONDS) // 30 second timeout | 1491 | def testRender = future.get(30, java.util.concurrent.TimeUnit.SECONDS) // 30 second timeout |
| 1461 | output = testRender.output | 1492 | output = testRender.output |
| 1462 | def outputLength = output?.length() ?: 0 | 1493 | def outputLength = output?.length() ?: 0 |
| 1463 | ec.logger.info("MCP Screen Execution: Successfully rendered screen ${screenPath}, output length: ${outputLength}") | 1494 | |
| 1495 | // SIZE PROTECTION: Check response size before returning | ||
| 1496 | def maxResponseSize = 1024 * 1024 // 1MB limit | ||
| 1497 | if (outputLength > maxResponseSize) { | ||
| 1498 | ec.logger.warn("MCP Screen Execution: Response too large for ${screenPath}: ${outputLength} bytes (limit: ${maxResponseSize} bytes)") | ||
| 1499 | |||
| 1500 | // Create truncated response with clear indication | ||
| 1501 | def truncatedOutput = output.substring(0, Math.min(maxResponseSize / 2, outputLength)) | ||
| 1502 | output = """SCREEN RESPONSE TRUNCATED | ||
| 1503 | |||
| 1504 | The screen '${screenPath}' generated a response that is too large for MCP processing: | ||
| 1505 | - Original size: ${outputLength} bytes | ||
| 1506 | - Size limit: ${maxResponseSize} bytes | ||
| 1507 | - Truncated to: ${truncatedOutput.length()} bytes | ||
| 1508 | |||
| 1509 | TRUNCATED CONTENT: | ||
| 1510 | ${truncatedOutput} | ||
| 1511 | |||
| 1512 | [Response truncated due to size limits. Consider using more specific screen parameters or limiting data ranges.]""" | ||
| 1513 | } | ||
| 1514 | |||
| 1515 | ec.logger.info("MCP Screen Execution: Successfully rendered screen ${screenPath}, output length: ${output?.length() ?: 0}") | ||
| 1464 | } catch (java.util.concurrent.TimeoutException e) { | 1516 | } catch (java.util.concurrent.TimeoutException e) { |
| 1465 | future.cancel(true) | 1517 | future.cancel(true) |
| 1466 | throw new Exception("Screen rendering timed out after 30 seconds for ${screenPath}") | 1518 | throw new Exception("Screen rendering timed out after 30 seconds for ${screenPath}") | ... | ... |
| ... | @@ -203,6 +203,13 @@ class CustomScreenTestImpl implements ScreenTest { | ... | @@ -203,6 +203,13 @@ class CustomScreenTestImpl implements ScreenTest { |
| 203 | if (authzDisabled) threadEci.artifactExecutionFacade.disableAuthz() | 203 | if (authzDisabled) threadEci.artifactExecutionFacade.disableAuthz() |
| 204 | // as this is used for server-side transition calls don't do tarpit checks | 204 | // as this is used for server-side transition calls don't do tarpit checks |
| 205 | threadEci.artifactExecutionFacade.disableTarpit() | 205 | threadEci.artifactExecutionFacade.disableTarpit() |
| 206 | |||
| 207 | // Ensure user is properly authenticated in the thread context | ||
| 208 | // This is critical for screen authentication checks | ||
| 209 | if (username != null && !username.isEmpty()) { | ||
| 210 | threadEci.userFacade.internalLoginUser(username) | ||
| 211 | } | ||
| 212 | |||
| 206 | renderInternal(threadEci, stri) | 213 | renderInternal(threadEci, stri) |
| 207 | threadEci.destroy() | 214 | threadEci.destroy() |
| 208 | } catch (Throwable t) { | 215 | } catch (Throwable t) { |
| ... | @@ -230,8 +237,17 @@ class CustomScreenTestImpl implements ScreenTest { | ... | @@ -230,8 +237,17 @@ class CustomScreenTestImpl implements ScreenTest { |
| 230 | // push context | 237 | // push context |
| 231 | ContextStack cs = eci.getContext() | 238 | ContextStack cs = eci.getContext() |
| 232 | cs.push() | 239 | cs.push() |
| 240 | |||
| 241 | // Ensure user context is properly set in session attributes for WebFacadeStub | ||
| 242 | def sessionAttributes = new HashMap(sti.sessionAttributes) | ||
| 243 | sessionAttributes.putAll([ | ||
| 244 | userId: eci.userFacade.getUserId(), | ||
| 245 | username: eci.userFacade.getUsername(), | ||
| 246 | userAccountId: eci.userFacade.getUserId() | ||
| 247 | ]) | ||
| 248 | |||
| 233 | // create our custom WebFacadeStub instead of framework's, passing screen path for proper path handling | 249 | // create our custom WebFacadeStub instead of framework's, passing screen path for proper path handling |
| 234 | WebFacadeStub wfs = new WebFacadeStub(sti.ecfi, stri.parameters, sti.sessionAttributes, stri.requestMethod, stri.screenPath) | 250 | WebFacadeStub wfs = new WebFacadeStub(sti.ecfi, stri.parameters, sessionAttributes, stri.requestMethod, stri.screenPath) |
| 235 | // set stub on eci, will also put parameters in context | 251 | // set stub on eci, will also put parameters in context |
| 236 | eci.setWebFacade(wfs) | 252 | eci.setWebFacade(wfs) |
| 237 | // make the ScreenRender | 253 | // make the ScreenRender | ... | ... |
| ... | @@ -14,8 +14,8 @@ | ... | @@ -14,8 +14,8 @@ |
| 14 | package org.moqui.mcp | 14 | package org.moqui.mcp |
| 15 | 15 | ||
| 16 | import groovy.json.JsonSlurper | 16 | import groovy.json.JsonSlurper |
| 17 | import groovy.json.JsonOutput | ||
| 18 | import org.moqui.impl.context.ExecutionContextFactoryImpl | 17 | import org.moqui.impl.context.ExecutionContextFactoryImpl |
| 18 | import groovy.json.JsonBuilder | ||
| 19 | import org.moqui.context.ArtifactAuthorizationException | 19 | import org.moqui.context.ArtifactAuthorizationException |
| 20 | import org.moqui.context.ArtifactTarpitException | 20 | import org.moqui.context.ArtifactTarpitException |
| 21 | import org.moqui.impl.context.ExecutionContextImpl | 21 | import org.moqui.impl.context.ExecutionContextImpl |
| ... | @@ -155,11 +155,11 @@ try { | ... | @@ -155,11 +155,11 @@ try { |
| 155 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) | 155 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) |
| 156 | response.setContentType("application/json") | 156 | response.setContentType("application/json") |
| 157 | response.setHeader("WWW-Authenticate", "Basic realm=\"Moqui MCP\"") | 157 | response.setHeader("WWW-Authenticate", "Basic realm=\"Moqui MCP\"") |
| 158 | response.writer.write(groovy.json.JsonOutput.toJson([ | 158 | response.writer.write(new JsonBuilder([ |
| 159 | jsonrpc: "2.0", | 159 | jsonrpc: "2.0", |
| 160 | error: [code: -32003, message: "Authentication required. Use Basic auth with valid Moqui credentials."], | 160 | error: [code: -32003, message: "Authentication required. Use Basic auth with valid Moqui credentials."], |
| 161 | id: null | 161 | id: null |
| 162 | ])) | 162 | ]).toString()) |
| 163 | return | 163 | return |
| 164 | } | 164 | } |
| 165 | 165 | ||
| ... | @@ -253,11 +253,11 @@ try { | ... | @@ -253,11 +253,11 @@ try { |
| 253 | logger.warn("Enhanced MCP Access Forbidden (no authz): " + e.message) | 253 | logger.warn("Enhanced MCP Access Forbidden (no authz): " + e.message) |
| 254 | response.setStatus(HttpServletResponse.SC_FORBIDDEN) | 254 | response.setStatus(HttpServletResponse.SC_FORBIDDEN) |
| 255 | response.setContentType("application/json") | 255 | response.setContentType("application/json") |
| 256 | response.writer.write(groovy.json.JsonOutput.toJson([ | 256 | response.writer.write(new JsonBuilder([ |
| 257 | jsonrpc: "2.0", | 257 | jsonrpc: "2.0", |
| 258 | error: [code: -32001, message: "Access Forbidden: " + e.message], | 258 | error: [code: -32001, message: "Access Forbidden: " + e.message], |
| 259 | id: null | 259 | id: null |
| 260 | ])) | 260 | ]).toString()) |
| 261 | } catch (ArtifactTarpitException e) { | 261 | } catch (ArtifactTarpitException e) { |
| 262 | logger.warn("Enhanced MCP Too Many Requests (tarpit): " + e.message) | 262 | logger.warn("Enhanced MCP Too Many Requests (tarpit): " + e.message) |
| 263 | response.setStatus(429) | 263 | response.setStatus(429) |
| ... | @@ -265,20 +265,20 @@ try { | ... | @@ -265,20 +265,20 @@ try { |
| 265 | response.addIntHeader("Retry-After", e.getRetryAfterSeconds()) | 265 | response.addIntHeader("Retry-After", e.getRetryAfterSeconds()) |
| 266 | } | 266 | } |
| 267 | response.setContentType("application/json") | 267 | response.setContentType("application/json") |
| 268 | response.writer.write(groovy.json.JsonOutput.toJson([ | 268 | response.writer.write(new JsonBuilder([ |
| 269 | jsonrpc: "2.0", | 269 | jsonrpc: "2.0", |
| 270 | error: [code: -32002, message: "Too Many Requests: " + e.message], | 270 | error: [code: -32002, message: "Too Many Requests: " + e.message], |
| 271 | id: null | 271 | id: null |
| 272 | ])) | 272 | ]).toString()) |
| 273 | } catch (Throwable t) { | 273 | } catch (Throwable t) { |
| 274 | logger.error("Error in Enhanced MCP request", t) | 274 | logger.error("Error in Enhanced MCP request", t) |
| 275 | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) | 275 | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) |
| 276 | response.setContentType("application/json") | 276 | response.setContentType("application/json") |
| 277 | response.writer.write(groovy.json.JsonOutput.toJson([ | 277 | response.writer.write(new JsonBuilder([ |
| 278 | jsonrpc: "2.0", | 278 | jsonrpc: "2.0", |
| 279 | error: [code: -32603, message: "Internal error: " + t.message], | 279 | error: [code: -32603, message: "Internal error: " + t.message], |
| 280 | id: null | 280 | id: null |
| 281 | ])) | 281 | ]).toString()) |
| 282 | } finally { | 282 | } finally { |
| 283 | ec.destroy() | 283 | ec.destroy() |
| 284 | } | 284 | } |
| ... | @@ -397,7 +397,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -397,7 +397,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 397 | // Set MCP session ID header per specification BEFORE sending any data | 397 | // Set MCP session ID header per specification BEFORE sending any data |
| 398 | response.setHeader("Mcp-Session-Id", visit.visitId.toString()) | 398 | response.setHeader("Mcp-Session-Id", visit.visitId.toString()) |
| 399 | 399 | ||
| 400 | sendSseEvent(response.writer, "connect", groovy.json.JsonOutput.toJson(connectData), 0) | 400 | sendSseEvent(response.writer, "connect", new JsonBuilder(connectData).toString(), 0) |
| 401 | 401 | ||
| 402 | // Send endpoint info for message posting (for compatibility) | 402 | // Send endpoint info for message posting (for compatibility) |
| 403 | sendSseEvent(response.writer, "endpoint", "/mcp", 1) | 403 | sendSseEvent(response.writer, "endpoint", "/mcp", 1) |
| ... | @@ -414,7 +414,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -414,7 +414,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 414 | sessionId: visit.visitId, | 414 | sessionId: visit.visitId, |
| 415 | architecture: "Visit-based sessions" | 415 | architecture: "Visit-based sessions" |
| 416 | ] | 416 | ] |
| 417 | sendSseEvent(response.writer, "ping", groovy.json.JsonOutput.toJson(pingData), pingCount + 2) | 417 | sendSseEvent(response.writer, "ping", new JsonBuilder(pingData).toString(), pingCount + 2) |
| 418 | pingCount++ | 418 | pingCount++ |
| 419 | } | 419 | } |
| 420 | } | 420 | } |
| ... | @@ -432,7 +432,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -432,7 +432,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 432 | sessionId: visit.visitId, | 432 | sessionId: visit.visitId, |
| 433 | timestamp: System.currentTimeMillis() | 433 | timestamp: System.currentTimeMillis() |
| 434 | ] | 434 | ] |
| 435 | sendSseEvent(response.writer, "disconnect", groovy.json.JsonOutput.toJson(closeData), -1) | 435 | sendSseEvent(response.writer, "disconnect", new JsonBuilder(closeData).toString(), -1) |
| 436 | } catch (Exception e) { | 436 | } catch (Exception e) { |
| 437 | // Ignore errors during cleanup | 437 | // Ignore errors during cleanup |
| 438 | } | 438 | } |
| ... | @@ -460,7 +460,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -460,7 +460,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 460 | response.setContentType("application/json") | 460 | response.setContentType("application/json") |
| 461 | response.setCharacterEncoding("UTF-8") | 461 | response.setCharacterEncoding("UTF-8") |
| 462 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 462 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 463 | response.writer.write(groovy.json.JsonOutput.toJson([ | 463 | response.writer.write(JsonOutput.toJson([ |
| 464 | error: "Missing sessionId parameter or header", | 464 | error: "Missing sessionId parameter or header", |
| 465 | architecture: "Visit-based sessions" | 465 | architecture: "Visit-based sessions" |
| 466 | ])) | 466 | ])) |
| ... | @@ -477,7 +477,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -477,7 +477,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 477 | response.setContentType("application/json") | 477 | response.setContentType("application/json") |
| 478 | response.setCharacterEncoding("UTF-8") | 478 | response.setCharacterEncoding("UTF-8") |
| 479 | response.setStatus(HttpServletResponse.SC_NOT_FOUND) | 479 | response.setStatus(HttpServletResponse.SC_NOT_FOUND) |
| 480 | response.writer.write(groovy.json.JsonOutput.toJson([ | 480 | response.writer.write(JsonOutput.toJson([ |
| 481 | error: "Session not found: " + sessionId, | 481 | error: "Session not found: " + sessionId, |
| 482 | architecture: "Visit-based sessions" | 482 | architecture: "Visit-based sessions" |
| 483 | ])) | 483 | ])) |
| ... | @@ -491,7 +491,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -491,7 +491,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 491 | response.setContentType("application/json") | 491 | response.setContentType("application/json") |
| 492 | response.setCharacterEncoding("UTF-8") | 492 | response.setCharacterEncoding("UTF-8") |
| 493 | response.setStatus(HttpServletResponse.SC_FORBIDDEN) | 493 | response.setStatus(HttpServletResponse.SC_FORBIDDEN) |
| 494 | response.writer.write(groovy.json.JsonOutput.toJson([ | 494 | response.writer.write(JsonOutput.toJson([ |
| 495 | error: "Access denied for session: " + sessionId + " (visit.userId=${visit.userId}, ec.user.userId=${ec.user.userId})", | 495 | error: "Access denied for session: " + sessionId + " (visit.userId=${visit.userId}, ec.user.userId=${ec.user.userId})", |
| 496 | architecture: "Visit-based sessions" | 496 | architecture: "Visit-based sessions" |
| 497 | ])) | 497 | ])) |
| ... | @@ -515,7 +515,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -515,7 +515,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 515 | response.setContentType("application/json") | 515 | response.setContentType("application/json") |
| 516 | response.setCharacterEncoding("UTF-8") | 516 | response.setCharacterEncoding("UTF-8") |
| 517 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 517 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 518 | response.writer.write(groovy.json.JsonOutput.toJson([ | 518 | response.writer.write(JsonOutput.toJson([ |
| 519 | jsonrpc: "2.0", | 519 | jsonrpc: "2.0", |
| 520 | error: [code: -32700, message: "Failed to read request body: " + e.message], | 520 | error: [code: -32700, message: "Failed to read request body: " + e.message], |
| 521 | id: null | 521 | id: null |
| ... | @@ -528,7 +528,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -528,7 +528,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 528 | response.setContentType("application/json") | 528 | response.setContentType("application/json") |
| 529 | response.setCharacterEncoding("UTF-8") | 529 | response.setCharacterEncoding("UTF-8") |
| 530 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 530 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 531 | response.writer.write(groovy.json.JsonOutput.toJson([ | 531 | response.writer.write(JsonOutput.toJson([ |
| 532 | jsonrpc: "2.0", | 532 | jsonrpc: "2.0", |
| 533 | error: [code: -32602, message: "Empty request body"], | 533 | error: [code: -32602, message: "Empty request body"], |
| 534 | id: null | 534 | id: null |
| ... | @@ -545,7 +545,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -545,7 +545,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 545 | response.setContentType("application/json") | 545 | response.setContentType("application/json") |
| 546 | response.setCharacterEncoding("UTF-8") | 546 | response.setCharacterEncoding("UTF-8") |
| 547 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 547 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 548 | response.writer.write(groovy.json.JsonOutput.toJson([ | 548 | response.writer.write(JsonOutput.toJson([ |
| 549 | jsonrpc: "2.0", | 549 | jsonrpc: "2.0", |
| 550 | error: [code: -32700, message: "Invalid JSON: " + e.message], | 550 | error: [code: -32700, message: "Invalid JSON: " + e.message], |
| 551 | id: null | 551 | id: null |
| ... | @@ -558,7 +558,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -558,7 +558,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 558 | response.setContentType("application/json") | 558 | response.setContentType("application/json") |
| 559 | response.setCharacterEncoding("UTF-8") | 559 | response.setCharacterEncoding("UTF-8") |
| 560 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 560 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 561 | response.writer.write(groovy.json.JsonOutput.toJson([ | 561 | response.writer.write(JsonOutput.toJson([ |
| 562 | jsonrpc: "2.0", | 562 | jsonrpc: "2.0", |
| 563 | error: [code: -32600, message: "Invalid JSON-RPC 2.0 request"], | 563 | error: [code: -32600, message: "Invalid JSON-RPC 2.0 request"], |
| 564 | id: rpcRequest?.id ?: null | 564 | id: rpcRequest?.id ?: null |
| ... | @@ -576,7 +576,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -576,7 +576,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 576 | response.setContentType("application/json") | 576 | response.setContentType("application/json") |
| 577 | response.setCharacterEncoding("UTF-8") | 577 | response.setCharacterEncoding("UTF-8") |
| 578 | response.setStatus(HttpServletResponse.SC_OK) | 578 | response.setStatus(HttpServletResponse.SC_OK) |
| 579 | response.writer.write(groovy.json.JsonOutput.toJson([ | 579 | response.writer.write(JsonOutput.toJson([ |
| 580 | jsonrpc: "2.0", | 580 | jsonrpc: "2.0", |
| 581 | id: rpcRequest.id, | 581 | id: rpcRequest.id, |
| 582 | result: [status: "processed", sessionId: sessionId, architecture: "Visit-based"] | 582 | result: [status: "processed", sessionId: sessionId, architecture: "Visit-based"] |
| ... | @@ -587,7 +587,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -587,7 +587,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 587 | response.setContentType("application/json") | 587 | response.setContentType("application/json") |
| 588 | response.setCharacterEncoding("UTF-8") | 588 | response.setCharacterEncoding("UTF-8") |
| 589 | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) | 589 | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) |
| 590 | response.writer.write(groovy.json.JsonOutput.toJson([ | 590 | response.writer.write(JsonOutput.toJson([ |
| 591 | jsonrpc: "2.0", | 591 | jsonrpc: "2.0", |
| 592 | error: [code: -32603, message: "Internal error: " + e.message], | 592 | error: [code: -32603, message: "Internal error: " + e.message], |
| 593 | id: null | 593 | id: null |
| ... | @@ -617,7 +617,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -617,7 +617,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 617 | if (!"POST".equals(method)) { | 617 | if (!"POST".equals(method)) { |
| 618 | response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED) | 618 | response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED) |
| 619 | response.setContentType("application/json") | 619 | response.setContentType("application/json") |
| 620 | response.writer.write(groovy.json.JsonOutput.toJson([ | 620 | response.writer.write(JsonOutput.toJson([ |
| 621 | jsonrpc: "2.0", | 621 | jsonrpc: "2.0", |
| 622 | error: [code: -32601, message: "Method Not Allowed. Use POST for JSON-RPC or GET /mcp-sse/sse for SSE."], | 622 | error: [code: -32601, message: "Method Not Allowed. Use POST for JSON-RPC or GET /mcp-sse/sse for SSE."], |
| 623 | id: null | 623 | id: null |
| ... | @@ -638,7 +638,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -638,7 +638,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 638 | if (!"POST".equals(jsonMethod)) { | 638 | if (!"POST".equals(jsonMethod)) { |
| 639 | response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED) | 639 | response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED) |
| 640 | response.setContentType("application/json") | 640 | response.setContentType("application/json") |
| 641 | response.writer.write(groovy.json.JsonOutput.toJson([ | 641 | response.writer.write(JsonOutput.toJson([ |
| 642 | jsonrpc: "2.0", | 642 | jsonrpc: "2.0", |
| 643 | error: [code: -32601, message: "Method Not Allowed. Use POST for JSON-RPC or GET /mcp-sse/sse for SSE."], | 643 | error: [code: -32601, message: "Method Not Allowed. Use POST for JSON-RPC or GET /mcp-sse/sse for SSE."], |
| 644 | id: null | 644 | id: null |
| ... | @@ -652,7 +652,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -652,7 +652,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 652 | if (!requestBody) { | 652 | if (!requestBody) { |
| 653 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 653 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 654 | response.setContentType("application/json") | 654 | response.setContentType("application/json") |
| 655 | response.writer.write(groovy.json.JsonOutput.toJson([ | 655 | response.writer.write(JsonOutput.toJson([ |
| 656 | jsonrpc: "2.0", | 656 | jsonrpc: "2.0", |
| 657 | error: [code: -32602, message: "Empty request body"], | 657 | error: [code: -32602, message: "Empty request body"], |
| 658 | id: null | 658 | id: null |
| ... | @@ -672,7 +672,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -672,7 +672,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 672 | logger.error("Failed to parse JSON-RPC request: ${e.message}") | 672 | logger.error("Failed to parse JSON-RPC request: ${e.message}") |
| 673 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 673 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 674 | response.setContentType("application/json") | 674 | response.setContentType("application/json") |
| 675 | response.writer.write(groovy.json.JsonOutput.toJson([ | 675 | response.writer.write(JsonOutput.toJson([ |
| 676 | jsonrpc: "2.0", | 676 | jsonrpc: "2.0", |
| 677 | error: [code: -32700, message: "Invalid JSON: " + e.message], | 677 | error: [code: -32700, message: "Invalid JSON: " + e.message], |
| 678 | id: null | 678 | id: null |
| ... | @@ -684,7 +684,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -684,7 +684,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 684 | if (!rpcRequest?.jsonrpc || rpcRequest.jsonrpc != "2.0" || !rpcRequest?.method) { | 684 | if (!rpcRequest?.jsonrpc || rpcRequest.jsonrpc != "2.0" || !rpcRequest?.method) { |
| 685 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 685 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 686 | response.setContentType("application/json") | 686 | response.setContentType("application/json") |
| 687 | response.writer.write(groovy.json.JsonOutput.toJson([ | 687 | response.writer.write(JsonOutput.toJson([ |
| 688 | jsonrpc: "2.0", | 688 | jsonrpc: "2.0", |
| 689 | error: [code: -32600, message: "Invalid JSON-RPC 2.0 request"], | 689 | error: [code: -32600, message: "Invalid JSON-RPC 2.0 request"], |
| 690 | id: null | 690 | id: null |
| ... | @@ -697,7 +697,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -697,7 +697,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 697 | if (protocolVersion && protocolVersion != "2025-06-18") { | 697 | if (protocolVersion && protocolVersion != "2025-06-18") { |
| 698 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 698 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 699 | response.setContentType("application/json") | 699 | response.setContentType("application/json") |
| 700 | response.writer.write(groovy.json.JsonOutput.toJson([ | 700 | response.writer.write(JsonOutput.toJson([ |
| 701 | jsonrpc: "2.0", | 701 | jsonrpc: "2.0", |
| 702 | error: [code: -32600, message: "Unsupported MCP protocol version: ${protocolVersion}. Supported: 2025-06-18"], | 702 | error: [code: -32600, message: "Unsupported MCP protocol version: ${protocolVersion}. Supported: 2025-06-18"], |
| 703 | id: null | 703 | id: null |
| ... | @@ -713,7 +713,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -713,7 +713,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 713 | if (!sessionId && rpcRequest.method != "initialize") { | 713 | if (!sessionId && rpcRequest.method != "initialize") { |
| 714 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) | 714 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST) |
| 715 | response.setContentType("application/json") | 715 | response.setContentType("application/json") |
| 716 | response.writer.write(groovy.json.JsonOutput.toJson([ | 716 | response.writer.write(JsonOutput.toJson([ |
| 717 | jsonrpc: "2.0", | 717 | jsonrpc: "2.0", |
| 718 | error: [code: -32600, message: "Mcp-Session-Id header required for non-initialize requests"], | 718 | error: [code: -32600, message: "Mcp-Session-Id header required for non-initialize requests"], |
| 719 | id: rpcRequest.id | 719 | id: rpcRequest.id |
| ... | @@ -733,7 +733,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -733,7 +733,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 733 | if (!existingVisit) { | 733 | if (!existingVisit) { |
| 734 | response.setStatus(HttpServletResponse.SC_NOT_FOUND) | 734 | response.setStatus(HttpServletResponse.SC_NOT_FOUND) |
| 735 | response.setContentType("application/json") | 735 | response.setContentType("application/json") |
| 736 | response.writer.write(groovy.json.JsonOutput.toJson([ | 736 | response.writer.write(JsonOutput.toJson([ |
| 737 | jsonrpc: "2.0", | 737 | jsonrpc: "2.0", |
| 738 | error: [code: -32600, message: "Session not found: ${sessionId}"], | 738 | error: [code: -32600, message: "Session not found: ${sessionId}"], |
| 739 | id: rpcRequest.id | 739 | id: rpcRequest.id |
| ... | @@ -745,7 +745,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -745,7 +745,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 745 | if (!existingVisit.userId || !ec.user.userId || existingVisit.userId.toString() != ec.user.userId.toString()) { | 745 | if (!existingVisit.userId || !ec.user.userId || existingVisit.userId.toString() != ec.user.userId.toString()) { |
| 746 | response.setStatus(HttpServletResponse.SC_FORBIDDEN) | 746 | response.setStatus(HttpServletResponse.SC_FORBIDDEN) |
| 747 | response.setContentType("application/json") | 747 | response.setContentType("application/json") |
| 748 | response.writer.write(groovy.json.JsonOutput.toJson([ | 748 | response.writer.write(JsonOutput.toJson([ |
| 749 | jsonrpc: "2.0", | 749 | jsonrpc: "2.0", |
| 750 | error: [code: -32600, message: "Access denied for session: ${sessionId}"], | 750 | error: [code: -32600, message: "Access denied for session: ${sessionId}"], |
| 751 | id: rpcRequest.id | 751 | id: rpcRequest.id |
| ... | @@ -761,7 +761,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -761,7 +761,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 761 | logger.error("Error finding session ${sessionId}: ${e.message}") | 761 | logger.error("Error finding session ${sessionId}: ${e.message}") |
| 762 | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) | 762 | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) |
| 763 | response.setContentType("application/json") | 763 | response.setContentType("application/json") |
| 764 | response.writer.write(groovy.json.JsonOutput.toJson([ | 764 | response.writer.write(JsonOutput.toJson([ |
| 765 | jsonrpc: "2.0", | 765 | jsonrpc: "2.0", |
| 766 | error: [code: -32603, message: "Session lookup error: ${e.message}"], | 766 | error: [code: -32603, message: "Session lookup error: ${e.message}"], |
| 767 | id: rpcRequest.id | 767 | id: rpcRequest.id |
| ... | @@ -788,7 +788,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") | ... | @@ -788,7 +788,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") |
| 788 | response.setContentType("application/json") | 788 | response.setContentType("application/json") |
| 789 | response.setCharacterEncoding("UTF-8") | 789 | response.setCharacterEncoding("UTF-8") |
| 790 | 790 | ||
| 791 | response.writer.write(groovy.json.JsonOutput.toJson(rpcResponse)) | 791 | response.writer.write(JsonOutput.toJson(rpcResponse)) |
| 792 | } | 792 | } |
| 793 | 793 | ||
| 794 | private Map<String, Object> processMcpMethod(String method, Map params, ExecutionContextImpl ec, String sessionId, def visit) { | 794 | private Map<String, Object> processMcpMethod(String method, Map params, ExecutionContextImpl ec, String sessionId, def visit) { | ... | ... |
| ... | @@ -67,15 +67,15 @@ class WebFacadeStub implements WebFacade { | ... | @@ -67,15 +67,15 @@ class WebFacadeStub implements WebFacade { |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | protected void createMockHttpObjects() { | 69 | protected void createMockHttpObjects() { |
| 70 | // Create mock HttpServletRequest | 70 | // Create mock HttpSession first |
| 71 | this.httpServletRequest = new MockHttpServletRequest(this.parameters, this.requestMethod) | 71 | this.httpSession = new MockHttpSession(this.sessionAttributes) |
| 72 | |||
| 73 | // Create mock HttpServletRequest with session | ||
| 74 | this.httpServletRequest = new MockHttpServletRequest(this.parameters, this.requestMethod, this.httpSession) | ||
| 72 | 75 | ||
| 73 | // Create mock HttpServletResponse with String output capture | 76 | // Create mock HttpServletResponse with String output capture |
| 74 | this.httpServletResponse = new MockHttpServletResponse() | 77 | this.httpServletResponse = new MockHttpServletResponse() |
| 75 | 78 | ||
| 76 | // Create mock HttpSession | ||
| 77 | this.httpSession = new MockHttpSession(this.sessionAttributes) | ||
| 78 | |||
| 79 | // Note: Objects are linked through the mock implementations | 79 | // Note: Objects are linked through the mock implementations |
| 80 | } | 80 | } |
| 81 | 81 | ||
| ... | @@ -256,13 +256,26 @@ class WebFacadeStub implements WebFacade { | ... | @@ -256,13 +256,26 @@ class WebFacadeStub implements WebFacade { |
| 256 | private final Map<String, Object> parameters | 256 | private final Map<String, Object> parameters |
| 257 | private final String method | 257 | private final String method |
| 258 | private HttpSession session | 258 | private HttpSession session |
| 259 | private String remoteUser = null | ||
| 260 | private java.security.Principal userPrincipal = null | ||
| 259 | 261 | ||
| 260 | MockHttpServletRequest(Map<String, Object> parameters, String method) { | 262 | MockHttpServletRequest(Map<String, Object> parameters, String method, HttpSession session = null) { |
| 261 | this.parameters = parameters ?: [:] | 263 | this.parameters = parameters ?: [:] |
| 262 | this.method = method ?: "GET" | 264 | this.method = method ?: "GET" |
| 263 | } | 265 | this.session = session |
| 264 | 266 | ||
| 265 | void setSession(HttpSession session) { this.session = session } | 267 | // Extract user information from session attributes for authentication |
| 268 | if (session) { | ||
| 269 | def username = session.getAttribute("username") | ||
| 270 | def userId = session.getAttribute("userId") | ||
| 271 | if (username) { | ||
| 272 | this.remoteUser = username as String | ||
| 273 | this.userPrincipal = new java.security.Principal() { | ||
| 274 | String getName() { return username as String } | ||
| 275 | } | ||
| 276 | } | ||
| 277 | } | ||
| 278 | } | ||
| 266 | 279 | ||
| 267 | @Override String getMethod() { return method } | 280 | @Override String getMethod() { return method } |
| 268 | @Override String getScheme() { return "http" } | 281 | @Override String getScheme() { return "http" } |
| ... | @@ -303,9 +316,9 @@ class WebFacadeStub implements WebFacade { | ... | @@ -303,9 +316,9 @@ class WebFacadeStub implements WebFacade { |
| 303 | @Override void removeAttribute(String name) {} | 316 | @Override void removeAttribute(String name) {} |
| 304 | @Override java.util.Enumeration<String> getAttributeNames() { return Collections.enumeration([]) } | 317 | @Override java.util.Enumeration<String> getAttributeNames() { return Collections.enumeration([]) } |
| 305 | @Override String getAuthType() { return null } | 318 | @Override String getAuthType() { return null } |
| 306 | @Override String getRemoteUser() { return null } | 319 | @Override String getRemoteUser() { return remoteUser } |
| 307 | @Override boolean isUserInRole(String role) { return false } | 320 | @Override boolean isUserInRole(String role) { return false } |
| 308 | @Override java.security.Principal getUserPrincipal() { return null } | 321 | @Override java.security.Principal getUserPrincipal() { return userPrincipal } |
| 309 | @Override String getRequestedSessionId() { return null } | 322 | @Override String getRequestedSessionId() { return null } |
| 310 | @Override StringBuffer getRequestURL() { return new StringBuffer("http://localhost:8080/test") } | 323 | @Override StringBuffer getRequestURL() { return new StringBuffer("http://localhost:8080/test") } |
| 311 | @Override String getPathInfo() { return "/test" } | 324 | @Override String getPathInfo() { return "/test" } | ... | ... |
-
Please register or sign in to post a comment