How to create URI without context-path using X-Forwarded-* headers? - java

I am trying to find a solution which will build a new link using X-Forwarded-* headers.
public class ApiUriBuilderTest {
private MockHttpServletRequest request = new MockHttpServletRequest();
private HttpRequest httpRequest = new ServletServerHttpRequest(request);
#Before
public void setUp() throws Exception {
request.setScheme("http");
request.setServerName("localhost");
request.setServerPort(80);
request.setRequestURI("/mvc-showcase");
request.addHeader("X-Forwarded-Proto", "https");
request.addHeader("X-Forwarded-Host", "84.198.58.199");
request.addHeader("X-Forwarded-Port", "443");
request.setContextPath("/mvc-showcase");
request.setServletPath("/app");
request.setRequestURI("/mvc-showcase/app/uri/of/request?hello=world&raw#my-frag");
httpRequest = new ServletServerHttpRequest(request);
}
#Test
public void test() {
String uri = ForwardedContextPathServletUriComponentsBuilder.fromRequest(request).build().toUriString();
assertThat(uri, is("https://84.198.58.199:443"));
}
#Test
public void test_uri_components_builder() throws URISyntaxException {
UriComponents result = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
assertEquals("https://84.198.58.199:443", result.toString());
}
But the returning value is "https://84.198.58.199/mvc-showcase/app/uri/of/request?hello=world&raw#my-frag". How can I possible get rid of context-path, setvlet-path and request uri?

#Test
public void test() {
String uri = ServletUriComponentsBuilder.fromRequest(request).replacePath("relativePath").replaceQuery(null).build().toUriString();
assertThat(uri, is("https://84.198.58.199:8080/relativePath"));
}
helped.

Related

How to mock this webClient using JUnit?

I'm trying to mock the following method:
public Mono<PResponse> pay(final String oId,final Double amount) {
return webClient
.put()
.uri("/order/{oId}/amount/{amount}",oId,amount)
.body(BodyInserts
.fromObject(PRequest))
.exchange()
.flatMap(
response -> {
if(response.statusCode().is4xxClientError()) {
// call error Function
} else {
return response
.bodyToMono(PResponse.class)
.flatMap(pResponse -> {
return Mono.just(pResposne)
});
}
}
);
}
For your information, webClient is a private Instance.
You can use MockWebServer.Here is an example, using code from this blog post:
Service
class ApiCaller {
private WebClient webClient;
ApiCaller(WebClient webClient) {
this.webClient = webClient;
}
Mono<SimpleResponseDto> callApi() {
return webClient.put()
.uri("/api/resource")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "customAuth")
.syncBody(new SimpleRequestDto())
.retrieve()
.bodyToMono(SimpleResponseDto.class);
}
}
Test
class ApiCallerTest {
private final MockWebServer mockWebServer = new MockWebServer();
private final ApiCaller apiCaller = new ApiCaller(WebClient.create(mockWebServer.url("/").toString()));
#AfterEach
void tearDown() throws IOException {
mockWebServer.shutdown();
}
#Test
void call() throws InterruptedException {
mockWebServer.enqueue(
new MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody("{\"y\": \"value for y\", \"z\": 789}")
);
SimpleResponseDto response = apiCaller.callApi().block();
assertThat(response, is(not(nullValue())));
assertThat(response.getY(), is("value for y"));
assertThat(response.getZ(), is(789));
RecordedRequest recordedRequest = mockWebServer.takeRequest();
//use method provided by MockWebServer to assert the request header
recordedRequest.getHeader("Authorization").equals("customAuth");
DocumentContext context = JsonPath.parse(recordedRequest.getBody().inputStream());
//use JsonPath library to assert the request body
assertThat(context, isJson(allOf(
withJsonPath("$.a", is("value1")),
withJsonPath("$.b", is(123))
)));
}
}

What is the correct way to capture the Http Status code with Spring AOP?

I am creating an aspect to register my application using org.springframework.web.bind.annotation.RestController like #Pointcut, this works perfectly when my class responds normally, but when an exception occurs for some reason, the returned httpStatus is always 200, even If my http response returns 500 when an error occurs, I think this is because RestController does not set the http status, but delegates it to the exception handler, how do I fix this and still have traceability on top of the restcontroller?
Follow my rest controller
#Slf4j
#RestController
#RequestMapping("/api/conta")
public class ContaResourceHTTP {
#JetpackMethod("Pagamento de conta")
#PostMapping("/pagamento")
public void realizarPagamento(#RequestBody DTOPagamento dtoPagamento) throws InterruptedException
{
}
#JetpackMethod("TransferĂȘncia entre bancos")
#PostMapping("/ted")
public void realizarTED(#RequestBody DTOPagamento dtoPagamento) throws java.lang.Exception
{
if(true)
throw new Exception("XXX");
//log.info(dtoPagamento.toString());
}
}
my AOP implementation:
#Aspect
#Component
#EnableAspectJAutoProxy(proxyTargetClass = true)
#Slf4j
public class MetricsAspect {
//#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
#Pointcut("execution(* javax.servlet.http.HttpServlet.*(..)) *)")
public void springBeanPointcut() {
}
#Autowired
Tracer tracer;
#Around("springBeanPointcut()")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
long inicioProcesso = System.currentTimeMillis();
joinPoint.proceed();
long finalProcesso = System.currentTimeMillis();
long duracaoProcesso = finalProcesso - inicioProcesso;
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
Metrics metricas = new Metrics();
metricas.setDuracaoMs(duracaoProcesso);
metricas.setDataHoraRequisicao(milissegundosToStringDate(inicioProcesso));
metricas.setDataHoraResposta(milissegundosToStringDate(finalProcesso));
metricas.setServidorOrigem(request.getRemoteAddr());
metricas.setPortaOrigem(request.getRemotePort());
metricas.setDominioAcesso(request.getLocalName());
metricas.setPortaAcesso(request.getLocalPort());
metricas.setUrlPath(request.getRequestURI());
metricas.setMetodoHttp(request.getMethod());
metricas.setIdTransacao(tracer.currentSpan().context().traceIdString());
metricas.setIdSpan(tracer.currentSpan().context().spanIdString());
metricas.setStatusHttp(response.getStatus());
log.info(JSONConversor.toJSON(metricas));
}
public String milissegundosToStringDate(long ms) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Date dataInicial = new Date(ms);
return dateFormat.format(dataInicial);
}
}
My exception handler:
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ExceptionControllerAdvice {
#ExceptionHandler({ Throwable.class })
public ResponseEntity<ApiError> handlerValidationException2(Throwable e) {
return new ResponseEntity<>(new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, e, traceRespostaAPI),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
After a while I was able to solve the problem with a solution that may not be the most elegant for the problem, basically I used two pointcuts, one in the restcontroller to intercept the #JetpackMethod annotation value and add it to the http response header with advice before and another around HttpServlet that really is the one who really gets back with the modified http status.
Here's the code below that solved my problem.
This class intercepts annotation and adds its value to the header.
#Aspect
#Component
public class InterceptRestAnnotationAspect {
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllerExecution() {}
#Before("restControllerExecution()")
public void setMetodoHttpHeader(JoinPoint joinPoint) throws Throwable {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
String origem = VerificadorOrigem.processarOrigem(joinPoint);
response.setHeader("nomeMetodo", origem);
}
}
This other class logs the servlet metrics I needed and can retrieve the value entered in the header earlier.
#Aspect
#Component
#Slf4j
public class MetricsAspect {
#Pointcut("execution(* javax.servlet.http.HttpServlet.*(..)) *)")
public void servletService() {
}
#Autowired
Tracer tracer;
#Around("servletService()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
long inicioProcesso = System.currentTimeMillis();
Object result = joinPoint.proceed();
long finalProcesso = System.currentTimeMillis();
long duracaoProcesso = finalProcesso - inicioProcesso;
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
Metrics metricas = new Metrics();
String funcionalidade = response.getHeader("nomeMetodo") == null ? "Indeterminada"
: response.getHeader("nomeMetodo");
metricas.setNivelLog("INFO");
metricas.setFuncionalidade(funcionalidade);
metricas.setDuracaoMs(duracaoProcesso);
metricas.setDataHoraRequisicao(ManipulaData.milissegundosToStringDate(inicioProcesso));
metricas.setDataHoraResposta(ManipulaData.milissegundosToStringDate(finalProcesso));
metricas.setServidorOrigem(request.getRemoteAddr());
metricas.setPortaOrigem(request.getRemotePort());
metricas.setDominioAcesso(request.getLocalName());
metricas.setPortaAcesso(request.getLocalPort());
metricas.setUrlPath(request.getRequestURI());
metricas.setMetodoHttp(request.getMethod());
metricas.setIdTransacao(tracer.currentSpan().context().traceIdString());
metricas.setIdSpan(tracer.currentSpan().context().spanIdString());
metricas.setStatusHttp(response.getStatus());
log.info(JSONConversor.toJSON(metricas));
return result;
}
}
I don't think the code after joinPoint.proceed(); gets executed in case of Exceptions.
You can have a different advice for execution in case of Exceptions:
#AfterThrowing(pointcut = "springBeanPointcut()", throwing = "e")
public void afterThrowingAdvice(JoinPoint jp, Exception e) {
....
}

How to create test for post request

I have a method which send post request in third party resource and returns CloseableHttpResponse result. I try (in first time) to implement junit test for my project.. I know how to test methods which returns simple objects but I have no idea - how to possible similar test method?
public CloseableHttpResponse POST(String path, Map<String, String> parameters) throws URISyntaxException, IOException {
List<NameValuePair> pairParameters = generateListOfNameValuePair(parameters);
URI uri = new URIBuilder()
.setScheme(SSL_SCHEME)
.setHost(HOST)
.setPath(path)
.build();
HttpRequestBase postMethod = new HttpPost(uri);
try {
((HttpPost) postMethod).setEntity(new UrlEncodedFormEntity(pairParameters, "UTF-8"));
} catch (UnsupportedEncodingException initE) {
initE.printStackTrace();
}
return session.getHttpClient().execute(postMethod, session.getHttpContext());
}
If you use spring boot, you can use Mockito unit test.
this is an example to show you how to implement mockMvc and unit test method.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
public class StockPlanControllerTest {
#Autowired
public WebApplicationContext context;
public MockMvc mockMvc;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void POST(String path, Map<String, String> parameters) throws URISyntaxException, IOException {
mockMvc.perform(post(path)
.contentType(MediaType.APPLICATION_JSON)
.param("paramkey", "paramvalue"))
.andExpect(status().isOk());
}
To learn more about Mockito unit test, this tutorial help you more.
mockito tutorial

RequestDispatcher did not work when used mockmvc in my test

I have a demo filter to log request/response body and verify the request signature, if signature is wrong the filter would be redirect to my error controller. And I have some test case to check this filter:
post the correct signature by using RestTemplate
post the wrong signature by using RestTemplate
post the correct signature by using MockMvc
post the wrong signature by using MockMvc
when I run that, case 1,2,3 were correct. but the case 4 has some problem.
I found that case 4 has run this code, but it did not redirect to my error controller.
RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher("/error/signError");
requestDispatcher.forward(requestWrapper, responseWrapper);
Whether I used the default MockMvc by #Autowired or used the MockMvcBuilders to create, this code did not work.
So, I confuse how much different about the MockMvc and RestTemplate, and is RequestDispatcher possible to work by using MockMvc?
The filter code:
#Component
public class ApiFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(ApiFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestId = UUID.randomUUID().toString();
String requestBody = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
LOG.info("request id [{}] requset body [{}]", requestId, requestBody);
ApiRequestWrapper requestWrapper = null;
ApiResponseWrapper responseWrapper = null;
try {
requestWrapper = new ApiRequestWrapper(requestId, request, requestBody);
responseWrapper = new ApiResponseWrapper(requestId, response);
System.out.println(request.getRequestURI());
System.out.println(request.getRequestURL());
System.out.println(request.getPathInfo());
System.out.println(getServletContext());
if (StringUtils.equalsIgnoreCase(request.getHeader(ApiConstant.HEAD_SIGN), DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN))) {
filterChain.doFilter(requestWrapper, responseWrapper);
} else {
// redirect to error response
RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher("/error/signError");
requestDispatcher.forward(requestWrapper, responseWrapper);
}
} finally {
LOG.info("request id [{}] response body [{}]", requestId, responseWrapper);
}
}
}
and my test case like this.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class LoginContorllerTest {
#Autowired ObjectMapper objectMapper;
//#Autowired
MockMvc mockMvc;
#Autowired TestRestTemplate restTemplate;
#Autowired WebApplicationContext webApplicationContext;
#Autowired ApiFilter apiFilter;
#Before
public void init() throws Exception {
MockFilterConfig filterConfig = new MockFilterConfig(webApplicationContext.getServletContext(), "apiFilter");
apiFilter.init(filterConfig);
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(apiFilter, "/api/*").build();
}
#Test
public void mockTest() throws Exception {
System.out.println("mockTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String sign = DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN);
String contentAsString = mockMvc
.perform(post("/api/login").header(ApiConstant.HEAD_SIGN, sign).contentType(MediaType.APPLICATION_JSON_UTF8).content(requestBody))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
// please forgive me using this way in the demo
System.out.println(contentAsString);
System.out.println();
}
#Test
public void mockSignErrorTest() throws Exception {
System.out.println("mockSignErrorTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String contentAsString = mockMvc
.perform(post("/api/login").header(ApiConstant.HEAD_SIGN, "12254548858").contentType(MediaType.APPLICATION_JSON_UTF8).content(requestBody))
.andReturn()
.getResponse()
.getContentAsString();
System.out.println(contentAsString);
System.out.println();
}
#Test
public void restTest() throws Exception {
System.out.println("restTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String sign = DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN);
HttpHeaders headers = new HttpHeaders();
headers.add(ApiConstant.HEAD_SIGN, sign);
HttpEntity<LoginParam> httpEntity = new HttpEntity<LoginParam>(param, headers);
ResponseEntity<String> result = this.restTemplate.exchange("/api/login", HttpMethod.POST, httpEntity, String.class);
System.out.println(result.getBody());
System.out.println();
}
#Test
public void restSignErrorTest() throws Exception {
System.out.println("restSignErrorTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
HttpHeaders headers = new HttpHeaders();
headers.add(ApiConstant.HEAD_SIGN, "123456789");
HttpEntity<LoginParam> httpEntity = new HttpEntity<LoginParam>(param, headers);
ResponseEntity<String> result = this.restTemplate.exchange("/api/login", HttpMethod.POST, httpEntity, String.class);
System.out.println(result.getBody());
System.out.println();
}
}

Testing Spring MultipartHttpServletRequest

Trying to test a spring controller that we have for multiple file upload. Here is the controller:
#RequestMapping("/vocabularys")
#Controller
public class VocabularyController {
...
The action I want to test:
#RequestMapping(value = "/import", method = {RequestMethod.PUT, RequestMethod.POST})
#ResponseBody
#CacheEvict(value="vocabulary", allEntries=true)
public Object importVocabulary(MultipartHttpServletRequest request, HttpServletResponse response) {
...
The resolver I have in the webmvc-config.xml:
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
The code works just fine and all. I'm running into problems when I am trying to unit/integration test this.
Here is my attempt at the test:
public class VocabularyControllerTest extends BaseControllerTest {
static final private String AdminUsername = "administrator";
#Test
public void shouldBeAbleToUploadAFile() throws Exception {
createTestWorkspace();
login(AdminUsername, "*");
MockMultipartFile file = new MockMultipartFile("test_vocab.xml", new FileInputStream("src/test/files/acme_vocabulary.xml"));
MockMultipartHttpServletRequestBuilder mockMultipartHttpServletRequestBuilder = (MockMultipartHttpServletRequestBuilder) fileUpload("/vocabularys/import").accept(MediaType.ALL).session(httpSession);
mockMultipartHttpServletRequestBuilder.file(file);
mockMultipartHttpServletRequestBuilder.content("whatever");
ResultActions resultActions = mockMvc.perform(mockMultipartHttpServletRequestBuilder);
resultActions.andExpect(status().isFound());
}
}
Ignore the createWorkspace() and login() and stuff - those are for passing through some security filters.
The relevant part of the BaseControllerTest:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy({
#ContextConfiguration(locations = {
"file:src/test/resources/META-INF/spring/applicationContext.xml",
"file:src/test/resources/META-INF/spring/applicationContext-security.xml",
"file:src/main/resources/META-INF/spring/applicationContext-database.xml",
"file:src/main/resources/META-INF/spring/applicationContext-activiti.xml",
"file:src/main/resources/META-INF/spring/applicationContext-cache.xml",
"file:src/main/resources/META-INF/spring/applicationContext-jms.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mail.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mongo.xml"}),
#ContextConfiguration(locations = {
"file:src/main/webapp/WEB-INF/spring/webmvc-config.xml",
"file:src/test/webapp/WEB-INF/spring/applicationContext-filters.xml"})
})
#Transactional
public class BaseControllerTest extends BaseTest {
#Autowired
WebApplicationContext wac;
#Autowired
MockHttpSession httpSession;
#Autowired
MockServletContext servletContext;
#Autowired
OpenEntityManagerInViewFilter openEntityManagerInViewFilter;
#Autowired
HiddenHttpMethodFilter hiddenHttpMethodFilter;
#Autowired
CharacterEncodingFilter characterEncodingFilter;
#Autowired
SessionFilter sessionFilter;
#Autowired
WorkflowAsSessionFilter workflowAsSessionFilter;
#Autowired
FilterChainProxy springSecurityFilterChain;
#Autowired
RequestFilter requestFilter;
MockMvc mockMvc;
protected static final String TestFileDir = "src/test/files/";
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(openEntityManagerInViewFilter, "/*")
.addFilter(hiddenHttpMethodFilter, "/*")
.addFilter(characterEncodingFilter, "/*")
.addFilter(sessionFilter, "/*")
.addFilter(workflowAsSessionFilter, "/*")
.addFilter(springSecurityFilterChain, "/*")
.addFilter(requestFilter, "/*")
.build();
servletContext.setContextPath("/");
Session session = Session.findBySessionId(httpSession.getId());
if (session == null) {
session = new Session();
session.setJsessionid(httpSession.getId());
session.persist();
}
}
...
The issue is that when I try debugging this, the perform action on the mockMvc object never hits my controller method. I thought it was an issue getting past our security filters (which is why I have all the login and stuff) but I tested other actions in the vocabulary controller and I am able to hit them just fine.
Thoughts? Ideas? Suggestions?
Alright, found the issue.
Spring's MockMultipartHttpServletRequestBuilder returns a MockHttpMultipartServletRequest object eventually.
What the browser does however is post a multipart-encoded request which then gets picked up and parsed by the CommonsMultipartResolver bean defined in the XML.
In the test however, since we are already posting a MockHttpMultipartServletRequest, we don't want the resolver parsing this, so all we got to do is have a profile where the resolver doesn't kick in.
What we have chosen to do however is end up constructing a MockHttpServletRequest that has multipart encoding and put it through the Spring filters so that we can also integration test the resolver kicking in.
Unfortunately I don't see any support/helper in the Spring testing lib which allows you to take a MockHttpServletRequest and addPart() to it, or something to that effect => handcoded browser emulation function :(
The simple way how to test multipart upload is use StandardServletMultipartResolver.
and for test use this code:
final MockPart profilePicture = new MockPart("profilePicture", "stview.jpg", "image/gif", "dsdsdsd".getBytes());
final MockPart userData = new MockPart("userData", "userData", "application/json", "{\"name\":\"test aida\"}".getBytes());
this.mockMvc.perform(
fileUpload("/endUsers/" + usr.getId().toString()).with(new RequestPostProcessor() {
#Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.addPart(profilePicture);
request.addPart(userData);
return request;
}
})
MockPart class
public class MockPart extends MockMultipartFile implements Part {
private Map<String, String> headers;
public MockPart(String name, byte[] content) {
super(name, content);
init();
}
public MockPart(String name, InputStream contentStream) throws IOException {
super(name, contentStream);
init();
}
public MockPart(String name, String originalFilename, String contentType, byte[] content) {
super(name, originalFilename, contentType, content);
init();
}
public MockPart(String name, String originalFilename, String contentType, InputStream contentStream) throws IOException {
super(name, originalFilename, contentType, contentStream);
init();
}
public void init() {
this.headers = new HashMap<String, String>();
if (getOriginalFilename() != null) {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"; filename=\"" + getOriginalFilename() + "\"");
} else {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"");
}
if (getContentType() != null) {
this.headers.put("Content-Type".toLowerCase(), getContentType());
}
}
#Override
public void write(String fileName) throws IOException {
}
#Override
public void delete() throws IOException {
}
#Override
public String getHeader(String name) {
return this.headers.get(name.toLowerCase());
}
#Override
public Collection<String> getHeaders(String name) {
List<String> res = new ArrayList<String>();
if (getHeader(name) != null) {
res.add(getHeader(name));
}
return res;
}
#Override
public Collection<String> getHeaderNames() {
return this.headers.keySet();
}
}

Categories

Resources