How to add a request interceptor to a feign client? - java

I want every time when I make a request through feign client, to set a specific header with my authenticated user.
This is my filter from which I get the authentication and set it to the spring security context:
#EnableEurekaClient
#SpringBootApplication
#EnableFeignClients
public class PerformanceApplication {
#Bean
public Filter requestDetailsFilter() {
return new RequestDetailsFilter();
}
public static void main(String[] args) {
SpringApplication.run(PerformanceApplication.class, args);
}
private class RequestDetailsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String userName = ((HttpServletRequest)servletRequest).getHeader("Z-User-Details");
String pass = ((HttpServletRequest)servletRequest).getHeader("X-User-Details");
if (pass != null)
pass = decrypt(pass);
SecurityContext secure = new SecurityContextImpl();
org.springframework.security.core.Authentication token = new UsernamePasswordAuthenticationToken(userName, pass);
secure. setAuthentication(token);
SecurityContextHolder.setContext(secure);
filterChain.doFilter(servletRequest, servletResponse);
}
#Override
public void destroy() {
}
}
private String decrypt(String str) {
try {
Cipher dcipher = new NullCipher();
// Decode base64 to get bytes
byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
// Decrypt
byte[] utf8 = dcipher.doFinal(dec);
// Decode using utf-8
return new String(utf8, "UTF8");
} catch (javax.crypto.BadPaddingException e) {
} catch (IllegalBlockSizeException e) {
} catch (UnsupportedEncodingException e) {
} catch (java.io.IOException e) {
}
return null;
}
}
This is my feign client:
#FeignClient("holiday-client")
public interface EmailClient {
#RequestMapping(value = "/api/email/send", method = RequestMethod.POST)
void sendEmail(#RequestBody Email email);
}
And here I have a request interceptor:
#Component
public class FeignRequestInterceptor implements RequestInterceptor {
private String headerValue;
public FeignRequestInterceptor() {
}
public FeignRequestInterceptor(String username, String password) {
this(username, password, ISO_8859_1);
}
public FeignRequestInterceptor(String username, String password, Charset charset) {
checkNotNull(username, "username");
checkNotNull(password, "password");
this.headerValue = "Basic " + base64encode((username + ":" + password).getBytes(charset));
}
private static String base64encode(byte[] bytes) {
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(bytes);
}
#Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("Authorization", headerValue);
}
}
I don't know how to configure this interceptor to my client and how to set the header with the username and password. How can I accomplish that ?

You don't really need your own implementation of the FeignRequestInterceptor as there is already BasicAuthRequestInterceptor in the feign.auth package that does exactly the same.
With this said, you basically have almost everything set up already. All is left to do is to define the basicAuthRequestInterceptor bean with specific username and password:
#Bean
public RequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("username", "password");
}

I know the thread is a bit old but wanted to give some explanation on what's happening here.
If you'd like to customize your Feign requests, you can use a RequestInterceptor. This can be a custom implementation or you can reuse what's available in the Feign library, e.g. BasicAuthRequestInterceptor.
How to register it? Well, there 2 ways to do it depending on how you use Feign.
If you're using plain Feign without Spring, then you gotta set the interceptor to the Feign builder. An example is here.
Feign.builder()
.requestInterceptor(new MyCustomInterceptor())
.target(MyClient.class, "http://localhost:8081");
If you're using Spring Cloud OpenFeign and you use the #FeignClient annotation to construct your clients, then you have to create a bean from your RequestInterceptor by either defining it as a #Component or as a #Bean in one of your #Configuration classes. Example here.
#Component
public class MyCustomInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
// do something
}
}
Also, you can check out one of my articles in this topic, maybe that clears it up better: Customizing each request with Spring Cloud Feign

Related

Set interceptor only shutdown endpoint

In Spring Boot version 2.2 I have an implementation of HandlerInterceptor and expose info, health, shutdown endpoints of Actuator.
I'd like to set that interceptor only for the shutdown endpoint, but not sure how to do it.
Already know that the code on the following page is useful for setting an interceptor for all endpoints.
https://github.com/spring-projects/spring-boot/issues/11234
#Configuration
public class ActuatorConfig extends WebMvcEndpointManagementContextConfiguration {
#Autowired
private AuthenticationInterceptor authenticationInterceptor;
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
WebMvcEndpointHandlerMapping mapping = super.webEndpointServletHandlerMapping(
endpointDiscoverer,
endpointMediaTypes,
corsProperties,
webEndpointProperties);
mapping.setInterceptors(authenticationInterceptor);
return mapping;
}
}
Also I checked this page
Spring Boot can't intercept actuator access
The comments from spring team on github issue link suggest to create a filter for intercepting actuator endpoint.
You can create a servlet filter and add authentication logic.
Here is a example:
#Component
public class ActuatorFilter implements Filter {
private final BasicPasswordEncryptor passwordEncryptor = new BasicPasswordEncryptor();
private String encryptedPassword;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
try {
File file = ResourceUtils.getFile("classpath:password.txt");
try (FileReader reader = new FileReader(file)) {
char[] chars = new char[(int) file.length()];
reader.read(chars);
encryptedPassword = new String(chars);
} catch (IOException e) {
e.printStackTrace();
}
}catch (FileNotFoundException e){
e.printStackTrace();
}
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getRequestURI().toString();
String rawPassword = request.getHeader("Authorization");
if(url.equals("/actuator/shutdown") && !passwordEncryptor.checkPassword(rawPassword,encryptedPassword)){
response.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter out = response.getWriter();
out.print("Invalid password");
out.flush();
return;
}
chain.doFilter(req, res);
}
#Override
public void destroy() { }
}
In the above code i have used jasypt for encrypting password and matching it in filter.
You can use new BasicPasswordEncryptor().encryptPassword(rawPassword); to get a encrypted password and save it in either DB or file. In the above code i have saved the encrypted password in password.txt in resources folder

how to force hostname when behind a chain of 2 revese proxy

An spring boot application is hosted behind 2 reverse proxy (chained).
reverse-proxy 1 --> reverse-proxy 2 --> spring boot app
And the host and forward headers are not chain correctly. there is a way to force the host to a fixed value? like the hostname of the "reverse proxy 1"?
i have fixed my issue by changing the serverName in incoming request.
i have add a valve to tomcat:
public class HostForceValve extends ValveBase {
private final String proxyName;
public HostForceValve(String proxyName) {
this.proxyName = proxyName;
}
#Override public void invoke(Request request, Response response) throws IOException, ServletException {
org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest();
MimeHeaders mimeHeaders = coyoteRequest.getMimeHeaders();
mimeHeaders.removeHeader("host");
final MessageBytes host = mimeHeaders.addValue("host");
host.setString(proxyName);
request.setRemoteHost(proxyName);
request.getCoyoteRequest().serverName().setString(proxyName);
try {
Valve next = getNext();
if (null == next) {
return;
}
next.invoke(request, response);
} finally {
request.setRemoteHost(proxyName);
}
}
}
And add this value to the tomcat embedded server:
#Component
public class MyTomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${proxyName:}")
private String proxyName;
#Override
public void customize(TomcatServletWebServerFactory factory) {
final Collection<Valve> currents = factory.getEngineValves();
final ArrayList<Valve> addValves = new ArrayList<>(currents);
if (StringUtils.hasLength(proxyName)) {
addValves.add(0, new HostForceValve(proxyName));
}
factory.setEngineValves(addValves);
}
}

Spring cloud config failed using filter

I'm using Spring Cloud Config to help build Rest Services and I choose GitHub to maintain my config files. There is a need when I change some config on GitHub its WebHooks will call API which Spring Cloud Config provided to monitor configuration change and notify Config Service.
In order to verify whether the request is a GitHub request. I will add a filter to check the signature, which does not work:
The request even doesn't through the filter and get a http 200 ok response. I also test the way to extends WebMvcConfigurerAdapter. But still got the same. I wonder is spring cloud config do sth before my filter can handle the request?
public class WebhookSignatureFilter implements Filter
{
#Autowired private WebhooksAuthService webhooksAuthService;
#Override public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException,
ServletException
{
HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
String githubSignature=httpServletRequest.getHeader("HTTP_X_HUB_SIGNATURE");
String payload=getPayLoad(httpServletRequest);
try
{
if(!webhooksAuthService.isValidWebhookSignature(githubSignature, payload))
{
throw new GeneralSecurityException();
}
}
catch(GeneralSecurityException e)
{
throw new ServletException("verify signature failed!");
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String getPayLoad(HttpServletRequest httpServletRequest) throws UnsupportedEncodingException
{
httpServletRequest.setCharacterEncoding("UTF-8");
BufferedReader bufferedReader=null;
StringBuffer payload=new StringBuffer();
String line;
try
{
InputStream inputStream=httpServletRequest.getInputStream();
bufferedReader=new BufferedReader(new InputStreamReader(inputStream));
while((line=bufferedReader.readLine()) != null)
{
payload.append(line);
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
if(bufferedReader!=null)
{
try
{
bufferedReader.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
return payload.toString();
}
#Override public void destroy()
{
}
#Override public void init(FilterConfig filterConfig) throws ServletException
{
}
}
here is my config file:
#Configuration public class FilterConfig
{
#Bean public FilterRegistrationBean webhooksFilter()
{
FilterRegistrationBean registration=new FilterRegistrationBean();
WebhookSignatureFilter webhookSignatureFilter=new WebhookSignatureFilter();
registration.setFilter(webhookSignatureFilter);
registration.addUrlPatterns("/monitor");
registration.setOrder(0);
return registration;
}
}
What am I doing wrong?
Problem solved. I found that when I checked the application.yml. I originally config rabbitmq ,github config mixed in the same yml file . So it may caused by the confuse yml file which may lead some config not loaded.

Restlet Client :: how to add filters?

I suffering of a lack of documentation on the use of Restlet at the client side.
I am getting a resource on server via a ClientResource:
new ClientResource(url).get();
But the server can return an ETag header. To handle this I want to save the ETag when returned and send it back to the server when using the same url.
Currently I am doing it like this:
ClientResource clientResource = new ClientResource(url);
addEtag(url, clientResource); // add the cached ETag to the query if any
clientResource.get();
saveEtag(url, clientResource); // cache the ETag if any
I would like to do this using the Restlet framework. I am searching for days wihtout understanding the missing link.
I can extend an application, overwrite the createOutboundRoot() method and return a filter:
public class RestLetClient extends Application {
private Client client;
// instantiation of the client and other things here
#Override
public Restlet createOutboundRoot() {
return new Filter(getContext(), client){
#Override
protected int beforeHandle(Request request, Response response) {
addEtag(request);
return super.doHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtag(request, reponse);
return super.afterHandle(request, response);
}
};
}
}
BUT how can I use this filtering around the Restlet client from my business code?
EDIT
The best I could get to work until now is this:
Request request = new Request(Method.GET, uri);
//the filter created in original post
filter.handle(request).getEntity();
This works but it is not integrated in the framework. What I am achieving to do is at the client side what is only documented for the server side. On the server you would do:
public class ServerApplication extends Application {
#Override
public Restlet createInboundRoot() {
Router router = new Router(getContext());
router.attach(GET_URL, GetResource.class);
return router;
}
}
and then start the server. The application will the be triggered on the reception of a GET request on the url.
What is the equivalent on the client side? How can I trigger a Client Application? If I have an Application running at the client side I can nicely add filters where they belong in a REST application
EDIT 2
When trying to run my client within an Application I get the error: The filter org.restlet.engine.application.RangeFilter#f372a7a was executed without a next Restlet attached to it.
Here is how I am getting the error. I have a class extending Application that is called from a JUnit test:
public class RestLetClient extends Application {
private final Client client;
Logger logger = LoggerFactory.getLogger(getClass());
public RestLetClient() {
this.client = new Client(Protocol.HTTP);
}
public Representation get(final String uri) throws Exception {
Request request = new Request(Method.GET, uri);
Response response = handle(request);
return response.getEntity();
}
#Override
public Restlet createOutboundRoot() {
return new Filter(getContext(), client) {
#Override
protected int beforeHandle(Request request, Response response) {
addEtagFilter(request);
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtagFilter(request, response);
super.afterHandle(request, response);
}
};
}
private void saveEtagFilter(Request request, Response response) {
logger.debug("saving etag");
}
private void addEtagFilter(Request request) {
logger.debug("adding etag");
}
}
and the unit with a single test method:
public class RestLetClientTest {
public static final String URL = "http://localhost:8123/resource";
private RestLetClient instance;
private Server server;
#Before
public void setUp() throws Exception {
server = new Server(Protocol.HTTP, 8123, new TestApplication());
server.start();
instance = new RestLetClient();
instance.start();
}
#After
public void tearDown() throws Exception {
instance.stop();
}
#Test
public void testGet() throws Exception {
Representation representation = instance.get(URL);
System.out.println(representation.getText());
}
private class TestApplication extends Application {
#Override
public Restlet createInboundRoot() {
return new Router().attach(RestLetClientTest.URL, GetResource.class);
}
}
private class GetResource extends ServerResource {
#Get
public Representation getResource() {
return new StringRepresentation("hello world");
}
}
}
What am I doing wrong?
I had a much nicer answer from a colleague. I post it here for the documentation.
The solution is to use a ClientResource, a Filter and a Client.
The Filter becomes the next() of the ClientResource and the Client the next() of the Filter.
public class ETagFilter extends Filter {
#Override
protected int beforeHandle(Request request, Response response) {
addEtag(request);
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtag(request, reponse);
super.afterHandle(request, response);
}
}
public class RestLetClient extends Application {
public Representation get(final String uri) throws Exception {
Client client = new Client(Protocol.HTTP);
ETagFilter eTagFilter = new ETagFilter();
clientResource = new ClientResource(uri);
clientResource.setNext(eTagFilter);
eTagFilter.setNext(client);
return clientResource.get(halMediaType);
}
}
For info. In my OP I was trying to transform code meant for server side into client side. That approach was wrong. My colleague pointed that the approach is much more like the use Apache HttpClient for similar needs
To have a client working you need to take the Application out of the picture since it is Server oriented according to the javadoc.
What you need is a Component, a ClientRouter and a custom ClientRoute.
Component manage connectors. A Restlet Client is a Connector.
ClientRouter dispatches to client connectors.
ClientRoute extends Filter allowing to add filters around your client handeling.
My solution:
The Component
public class RestLetComponent extends Component {
public RestLetComponent(Client client) {
getClients().add(client);
}
}
The ClientRouter
public class RestLetClientRouter extends ClientRouter {
public RestLetClientRouter(final Client client) {
super(new RestLetComponent(client));
ClientRoute clientRoute = new RestLetClientRoute(this, client);
//forcing to use only our custom route
getRoutes().clear();
getRoutes().add(clientRoute);
}
public Representation get(final String uri) throws Exception {
Request request = new Request(Method.GET, uri);
Response response = handle(request);
return response.getEntity();
}
}
And the custom ClientRoute that will add the filters
public class RestLetClientRoute extends ClientRoute {
Logger logger = LoggerFactory.getLogger(getClass());
public RestLetClientRoute(Router router, Client client) {
super(router, client);
}
//the filters
#Override
protected int beforeHandle(Request request, Response response) {
addEtagFilter(request);
return super.beforeHandle(request, response);
}
#Override
protected int doHandle(Request request, Response response) {
logger.debug("handling request: " + request.getMethod() + " - " + request.getResourceRef());
return super.doHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtagFilter(request, response);
super.afterHandle(request, response);
}
private void saveEtagFilter(Request request, Response response) {
logger.debug("saving etag");
}
private void addEtagFilter(Request request) {
logger.debug("adding etag");
}
}
And last but not least, I apologize to the Restlet authors, the documentation is there. I was reading the Restlet in Action book but the answer is in the very well documented javadoc.

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