Swagger 2 Feign client code oAuth flow throwing error url values must be not be absolute - oauth

I exposed Rest APIs, and I generated client code using Swagger 2 Java language with Feign library. The code gen generated the below OAuth RequestInterceptor. I am getting the below error when I use the oAuth as auth.
Error
feign.RetryableException: url values must be not be absolute.
at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:95)
at com.sam.feign.auth.OAuth.apply(OAuth.java:83)
at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:161)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:110)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at com.sun.proxy.$Proxy9.getUser(Unknown Source)
at com.sam.feign.clients.UserApiTest.getUserTest(UserApiTest.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.IllegalArgumentException: url values must be not be absolute.
at feign.RequestTemplate.uri(RequestTemplate.java:434)
at feign.RequestTemplate.uri(RequestTemplate.java:421)
at feign.RequestTemplate.append(RequestTemplate.java:388)
at com.sam.feign.auth.OAuth$OAuthFeignClient.execute(OAuth.java:163)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:65)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:55)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:71)
at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:93)
... 34 more
Swagger Generated oAuth supporting file
public class OAuth implements RequestInterceptor {
static final int MILLIS_PER_SECOND = 1000;
public interface AccessTokenListener {
void notify(BasicOAuthToken token);
}
private volatile String accessToken;
private Long expirationTimeMillis;
private OAuthClient oauthClient;
private TokenRequestBuilder tokenRequestBuilder;
private AuthenticationRequestBuilder authenticationRequestBuilder;
private AccessTokenListener accessTokenListener;
public OAuth(Client client, TokenRequestBuilder requestBuilder) {
this.oauthClient = new OAuthClient(new OAuthFeignClient(client));
this.tokenRequestBuilder = requestBuilder;
}
public OAuth(Client client, OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(client, OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes));
switch(flow) {
case accessCode:
case implicit:
tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
break;
case password:
tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
break;
case application:
tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
break;
default:
break;
}
authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl);
}
public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(new Client.Default(null, null), flow, authorizationUrl, tokenUrl, scopes);
}
#Override
public void apply(RequestTemplate template) {
// If the request already have an authorization (eg. Basic auth), do nothing
if (template.headers().containsKey("Authorization")) {
return;
}
// If first time, get the token
if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) {
updateAccessToken(template);
}
if (getAccessToken() != null) {
template.header("Authorization", "Bearer " + getAccessToken());
}
}
public synchronized void updateAccessToken(RequestTemplate template) {
OAuthJSONAccessTokenResponse accessTokenResponse;
try {
accessTokenResponse = oauthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
} catch (Exception e) {
throw new RetryableException(400, e.getMessage(), template.request().httpMethod(), e, null, template.request());
}
if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn());
if (accessTokenListener != null) {
accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken());
}
}
}
public synchronized void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
this.accessTokenListener = accessTokenListener;
}
public synchronized String getAccessToken() {
return accessToken;
}
public synchronized void setAccessToken(String accessToken, Long expiresIn) {
this.accessToken = accessToken;
this.expirationTimeMillis = System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND;
}
public TokenRequestBuilder getTokenRequestBuilder() {
return tokenRequestBuilder;
}
public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
this.tokenRequestBuilder = tokenRequestBuilder;
}
public AuthenticationRequestBuilder getAuthenticationRequestBuilder() {
return authenticationRequestBuilder;
}
public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) {
this.authenticationRequestBuilder = authenticationRequestBuilder;
}
public OAuthClient getOauthClient() {
return oauthClient;
}
public void setOauthClient(OAuthClient oauthClient) {
this.oauthClient = oauthClient;
}
public void setOauthClient(Client client) {
this.oauthClient = new OAuthClient( new OAuthFeignClient(client));
}
public static class OAuthFeignClient implements HttpClient {
private Client client;
public OAuthFeignClient() {
this.client = new Client.Default(null, null);
}
public OAuthFeignClient(Client client) {
this.client = client;
}
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
RequestTemplate req = new RequestTemplate()
.append(request.getLocationUri())
.method(requestMethod)
.body(request.getBody());
for (Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
Response feignResponse;
String body = "";
try {
feignResponse = client.execute(req.request(), new Options());
body = Util.toString(feignResponse.body().asReader());
} catch (IOException e) {
throw new OAuthSystemException(e);
}
String contentType = null;
Collection<String> contentTypeHeader = feignResponse.headers().get("Content-Type");
if(contentTypeHeader != null) {
contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
}
return OAuthClientResponseFactory.createCustomResponse(
body,
contentType,
feignResponse.status(),
responseClass
);
}
public void shutdown() {
// Nothing to do here
}
}
}
ApiClient.java have the below absolute URL which configured in swagger spec
public ApiClient() {
objectMapper = createObjectMapper();
apiAuthorizations = new LinkedHashMap<String, RequestInterceptor>();
feignBuilder = Feign.builder()
.encoder(new FormEncoder(new JacksonEncoder(objectMapper)))
.decoder(new JacksonDecoder(objectMapper))
.logger(new Slf4jLogger());
}
public ApiClient(String[] authNames) {
this();
for(String authName : authNames) {
RequestInterceptor auth = null;
if ("client-credentils-oauth2".equals(authName)) {
auth = new OAuth(OAuthFlow.application, "", "http://localhost:8080/app/oauth/token", "user.create");
} else if ("password-oauth2".equals(authName)) {
auth = new OAuth(OAuthFlow.password, "", "http://localhost:8080/app/oauth/token", "openid");
} else {
throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names");
}
addAuthorization(authName, auth);
}
}
Used the below dependencies
swagger-codegen-maven-plugin v2.4.28
feign-version 11.6
feign-form-version 3.8.0
oltu-version 1.0.1
Java 8
I am invoking the client by using below code
UserApi api = new ApiClient("client-credentils-oauth2","admin", "admin", null, null).buildClient(UserApi.class);
api.getUser(login, tenant)
I made the few changes in the generated oAuth.java file to make it work. Expecting the client generated code should work without making any manual changes.
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
// Added the below 3 lines
URI targetUri = URI.create(uri);
String target = targetUri.getScheme() + "://" + targetUri.getAuthority() ;
String path = targetUri.getPath();
RequestTemplate req = new RequestTemplate()
.uri(path)
.method(requestMethod)
.body(request.getBody())
.target(target); // Added this line
for (Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
req = req.resolve(new HashMap<String, Object>()); // Added this line
Response feignResponse;
String body = "";
try {
feignResponse = client.execute(req.request(), new Options());
body = Util.toString(feignResponse.body().asReader());
} catch (IOException e) {
throw new OAuthSystemException(e);
}
String contentType = null;
Collection<String> contentTypeHeader = feignResponse.headers().get("Content-Type");
if(contentTypeHeader != null) {
contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
}
return OAuthClientResponseFactory.createCustomResponse(
body,
contentType,
feignResponse.status(),
responseClass
);
}
Appreciate if someone can assist me with this issue

Related

Return value from Jenkins plugin

I'm developing a Jenkins plugin and in one of my build steps, I need to return a value. In this build step, I'm sending an API call to generate a registration token and I want to return that token as the output of this build step. The idea is to use this generated token later in pipeline/free-style.
My question is, how do I do that?
Here's my Build class:
package **.********.plugins;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.*;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Builder;
import hudson.util.ListBoxModel;
import **.********.constants.Constants;
import **.********.helpers.ApiHelper;
import **.********.helpers.ApiResponse;
import **.********.helpers.LogHelper;
import **.********.model.AgentDockerConfigData;
import **.********.model.AgentDockerConfigGenerationRequestData;
import **.********.model.JobData;
import **.********.model.ProjectData;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.HashMap;
public class GenerateAgentConfigToken extends Builder implements SimpleBuildStep {
//region Private members
private ApiHelper apiHelper;
private String alias;
private String projectId;
private String jobId;
private String browsers;
private AgentDockerConfigData config;
//endregion
//region Constructors
public GenerateAgentConfigToken() { }
#DataBoundConstructor
public GenerateAgentConfigToken(String alias, String projectId, String jobId, String browsers) {
this.alias = alias;
this.projectId = projectId;
this.jobId = jobId;
this.browsers = browsers;
}
//endregion
//region Setters & Getters
public String getAlias() {
return alias;
}
#DataBoundSetter
public void setAlias(String alias) {
this.alias = alias;
}
public String getProjectId() {
return projectId;
}
#DataBoundSetter
public void setProjectId(String projectId) {
this.projectId = projectId;
}
public String getJobId() {
return jobId;
}
#DataBoundSetter
public void setJobId(String jobId) {
this.jobId = jobId;
}
public String getBrowsers() {
return browsers;
}
#DataBoundSetter
public void setBrowsers(String browsers) {
this.browsers = browsers;
}
//endregion
private void init() {
LogHelper.Debug("Initializing API helper...");
this.apiHelper = new ApiHelper(PluginConfiguration.DESCRIPTOR.getApiKey());
}
#Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
#Override
public void perform(#Nonnull Run<?, ?> run, #Nonnull FilePath filePath, #Nonnull Launcher launcher, #Nonnull TaskListener taskListener) throws InterruptedException, IOException {
try {
EnvVars envVars = new EnvVars();
envVars = run.getEnvironment(taskListener);
envVars.put("jobId", jobId);
init();
LogHelper.SetLogger(taskListener.getLogger(), PluginConfiguration.DESCRIPTOR.isVerbose());
generateAgentConfigToken();
} catch (Exception e) {
LogHelper.Error(e);
run.setResult(Result.FAILURE);
}
}
private void generateAgentConfigToken() throws IOException {
LogHelper.Info("Sending a request to generate agent configuration token...");
//TODO: Change the URL to the production URL
ApiResponse<AgentDockerConfigData> response = apiHelper.Post(
Constants.TP_GENERATE_AGENT_CONFIG_TOKEN_URL,
null,
null,
generateRequestBody(),
AgentDockerConfigData.class);
if (response.isSuccessful()) {
if (response.getData() != null) {
config = response.getData();
}
} else {
int statusCode = response.getStatusCode();
String responseMessage = response.getMessage();
String message = "Unable to generate agent configuration token" + (statusCode > 0 ? " - " + statusCode : "") + (responseMessage != null ? " - " + responseMessage : "");
throw new hudson.AbortException(message);
}
}
private AgentDockerConfigGenerationRequestData generateRequestBody() {
// if the user did not provide an alias and jobId, send the body as null
if (StringUtils.isEmpty(alias) && StringUtils.isEmpty(jobId))
return null;
AgentDockerConfigGenerationRequestData body = new AgentDockerConfigGenerationRequestData();
if (!StringUtils.isEmpty(alias))
body.setAlias(alias);
if (!StringUtils.isEmpty(jobId)) {
body.setJobId(jobId);
if (!StringUtils.isEmpty(browsers))
body.setBrowsers(browsers.split(","));
}
return body;
}
#Override
public DescriptorImpl getDescriptor() { return (DescriptorImpl) super.getDescriptor(); }
#Extension
#Symbol(Constants.TP_GENERATE_AGENT_CONFIG_TOKEN_SYMBOL)
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
public DescriptorImpl() {
load();
}
#Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
req.bindJSON(this, formData);
save();
return super.configure(req, formData);
}
#Override
public boolean isApplicable(#SuppressWarnings("rawtypes") Class<? extends AbstractProject> jobType) {
return true;
}
#Nonnull
#Override
public String getDisplayName() {
return Constants.TP_GENERATE_AGENT_CONFIG_TOKEN;
}
public ListBoxModel doFillProjectIdItems() {
HashMap<String, Object> headers = new HashMap<>();
headers.put(Constants.ACCEPT, Constants.APPLICATION_JSON);
ApiResponse<ProjectData[]> response = null;
try {
ApiHelper apiHelper = new ApiHelper(PluginConfiguration.DESCRIPTOR.getApiKey());
response = apiHelper.Get(Constants.TP_RETURN_ACCOUNT_PROJECTS, headers, ProjectData[].class);
if (!response.isSuccessful()) {
int statusCode = response.getStatusCode();
String responseMessage = response.getMessage();
String message = "Unable to fetch the projects list" + (statusCode > 0 ? " - " + statusCode : "") + (responseMessage != null ? " - " + responseMessage : "");
throw new hudson.AbortException(message);
}
ListBoxModel model = new ListBoxModel();
model.add("Select a project", "");
for (ProjectData project : response.getData()) {
model.add(
project.getName() + " [" + project.getId() + "]",
project.getId());
}
return model;
} catch (IOException | NullPointerException e) {
LogHelper.Error(e);
}
return null;
}
public ListBoxModel doFillJobIdItems(#QueryParameter String projectId) {
if (projectId.isEmpty()) {
return new ListBoxModel();
}
HashMap<String, Object> headers = new HashMap<>();
headers.put(Constants.ACCEPT, Constants.APPLICATION_JSON);
ApiResponse<JobData[]> response = null;
try {
ApiHelper apiHelper = new ApiHelper(PluginConfiguration.DESCRIPTOR.getApiKey());
response = apiHelper.Get(String.format(Constants.TP_RETURN_PROJECT_JOBS, projectId), headers, JobData[].class);
if (!response.isSuccessful()) {
int statusCode = response.getStatusCode();
String responseMessage = response.getMessage();
String message = "Unable to fetch the project's jobs list" + (statusCode > 0 ? " - " + statusCode : "") + (responseMessage != null ? " - " + responseMessage : "");
throw new hudson.AbortException(message);
}
ListBoxModel model = new ListBoxModel();
model.add("Select a job to execute from the selected project (You must select a project first)", "");
for (JobData job : response.getData()) {
model.add(
job.getName() + " [" + job.getId() + "]",
job.getId());
}
return model;
} catch (IOException | NullPointerException e) {
LogHelper.Error(e);
}
return null;
}
}
}
Is there a way to return anything from perform rather than void or boolean ?

FeignClient throws instead of returning ResponseEntity with error http status

As I'm using ResponseEntity<T> as return value for my FeignClient method, I was expecting it to return a ResponseEntity with 400 status if it's what the server returns. But instead it throws a FeignException.
How can I get a proper ResponseEntity instead of an Exception from FeignClient ?
Here is my FeignClient:
#FeignClient(value = "uaa", configuration = OauthFeignClient.Conf.class)
public interface OauthFeignClient {
#RequestMapping(
value = "/oauth/token",
method = RequestMethod.POST,
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
ResponseEntity<OauthTokenResponse> token(Map<String, ?> formParams);
class Conf {
#Value("${oauth.client.password}")
String oauthClientPassword;
#Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
}
}
}
and here how I use it:
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) {
Map<String, String> formData = new HashMap<>();
ResponseEntity<OauthTokenResponse> response = oauthFeignClient.token(formData);
//code never reached if contacted service returns a 400
...
}
By the way, solution I gave before works, but my initial intention is bad idea: an error is an error and should not be handled on nominal flow. Throwing an exception, like Feign does, and handling it with an #ExceptionHandler is a better way to go in Spring MVC world.
So two solutions:
add an #ExceptionHandler for FeignException
configure the FeignClient with an ErrorDecoder to translate the error in an Exception your business layer knows about (and already provide #ExceptionHandler for)
I prefer second solution because received error message structure is likely to change from a client to an other, so you can extract finer grained data from those error with a per-client error decoding.
FeignClient with conf (sorry for the noise introduced by feign-form)
#FeignClient(value = "uaa", configuration = OauthFeignClient.Config.class)
public interface OauthFeignClient {
#RequestMapping(
value = "/oauth/token",
method = RequestMethod.POST,
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
DefaultOAuth2AccessToken token(Map<String, ?> formParams);
#Configuration
class Config {
#Value("${oauth.client.password}")
String oauthClientPassword;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
#Bean
public Decoder springDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
}
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
}
#Bean
public ErrorDecoder uaaErrorDecoder(Decoder decoder) {
return (methodKey, response) -> {
try {
OAuth2Exception uaaException = (OAuth2Exception) decoder.decode(response, OAuth2Exception.class);
return new SroException(
uaaException.getHttpErrorCode(),
uaaException.getOAuth2ErrorCode(),
Arrays.asList(uaaException.getSummary()));
} catch (Exception e) {
return new SroException(
response.status(),
"Authorization server responded with " + response.status() + " but failed to parse error payload",
Arrays.asList(e.getMessage()));
}
};
}
}
}
Common business exception
public class SroException extends RuntimeException implements Serializable {
public final int status;
public final List<String> errors;
public SroException(final int status, final String message, final Collection<String> errors) {
super(message);
this.status = status;
this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SroException)) return false;
SroException sroException = (SroException) o;
return status == sroException.status &&
Objects.equals(super.getMessage(), sroException.getMessage()) &&
Objects.equals(errors, sroException.errors);
}
#Override
public int hashCode() {
return Objects.hash(status, super.getMessage(), errors);
}
}
Error handler (extracted from a ResponseEntityExceptionHandler extension)
#ExceptionHandler({SroException.class})
public ResponseEntity<Object> handleSroException(SroException ex) {
return new SroError(ex).toResponse();
}
Error response DTO
#XmlRootElement
public class SroError implements Serializable {
public final int status;
public final String message;
public final List<String> errors;
public SroError(final int status, final String message, final Collection<String> errors) {
this.status = status;
this.message = message;
this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
}
public SroError(final SroException e) {
this.status = e.status;
this.message = e.getMessage();
this.errors = Collections.unmodifiableList(e.errors);
}
protected SroError() {
this.status = -1;
this.message = null;
this.errors = null;
}
public ResponseEntity<Object> toResponse() {
return new ResponseEntity(this, HttpStatus.valueOf(this.status));
}
public ResponseEntity<Object> toResponse(HttpHeaders headers) {
return new ResponseEntity(this, headers, HttpStatus.valueOf(this.status));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SroError)) return false;
SroError sroException = (SroError) o;
return status == sroException.status &&
Objects.equals(message, sroException.message) &&
Objects.equals(errors, sroException.errors);
}
#Override
public int hashCode() {
return Objects.hash(status, message, errors);
}
}
Feign client usage notice how errors are transparently handled (no try / catch) thanks to #ControllerAdvice & #ExceptionHandler({SroException.class})
#RestController
#RequestMapping("/uaa")
public class AuthenticationController {
private static final BearerToken REVOCATION_TOKEN = new BearerToken("", 0L);
private final OauthFeignClient oauthFeignClient;
private final int refreshTokenValidity;
#Autowired
public AuthenticationController(
OauthFeignClient oauthFeignClient,
#Value("${oauth.ttl.refresh-token}") int refreshTokenValidity) {
this.oauthFeignClient = oauthFeignClient;
this.refreshTokenValidity = refreshTokenValidity;
}
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) {
Map<String, String> formData = new HashMap<>();
formData.put("grant_type", "password");
formData.put("client_id", "web-client");
formData.put("username", userCredentials.username);
formData.put("password", userCredentials.password);
formData.put("scope", "openid");
DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
return ResponseEntity.ok(new LoginTokenPair(
new BearerToken(response.getValue(), response.getExpiresIn()),
new BearerToken(response.getRefreshToken().getValue(), refreshTokenValidity)));
}
#PostMapping("/logout")
public ResponseEntity<LoginTokenPair> revokeTokens() {
return ResponseEntity
.ok(new LoginTokenPair(REVOCATION_TOKEN, REVOCATION_TOKEN));
}
#PostMapping("/refresh")
public ResponseEntity<BearerToken> refreshToken(#RequestHeader("refresh_token") String refresh_token) {
Map<String, String> formData = new HashMap<>();
formData.put("grant_type", "refresh_token");
formData.put("client_id", "web-client");
formData.put("refresh_token", refresh_token);
formData.put("scope", "openid");
DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
return ResponseEntity.ok(new BearerToken(response.getValue(), response.getExpiresIn()));
}
}
So, looking at source code, it seams that only solution is actually using feign.Response as return type for FeignClient methods and hand decoding the body with something like new ObjectMapper().readValue(response.body().asReader(), clazz) (with a guard on 2xx status of course because for error statuses, it's very likely that body is an error description and not a valid payload ;).
This makes possible to extract and forward status, header, body, etc. even if status is not in 2xx range.
Edit:
Here is a way to forward status, headers and mapped JSON body (if possible):
public static class JsonFeignResponseHelper {
private final ObjectMapper json = new ObjectMapper();
public <T> Optional<T> decode(Response response, Class<T> clazz) {
if(response.status() >= 200 && response.status() < 300) {
try {
return Optional.of(json.readValue(response.body().asReader(), clazz));
} catch(IOException e) {
return Optional.empty();
}
} else {
return Optional.empty();
}
}
public <T, U> ResponseEntity<U> toResponseEntity(Response response, Class<T> clazz, Function<? super T, ? extends U> mapper) {
Optional<U> payload = decode(response, clazz).map(mapper);
return new ResponseEntity(
payload.orElse(null),//didn't find a way to feed body with original content if payload is empty
convertHeaders(response.headers()),
HttpStatus.valueOf(response.status()));
}
public MultiValueMap<String, String> convertHeaders(Map<String, Collection<String>> responseHeaders) {
MultiValueMap<String, String> responseEntityHeaders = new LinkedMultiValueMap<>();
responseHeaders.entrySet().stream().forEach(e ->
responseEntityHeaders.put(e.getKey(), new ArrayList<>(e.getValue())));
return responseEntityHeaders;
}
}
that can be used as follow:
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) throws IOException {
Response response = oauthFeignClient.token();
return feignHelper.toResponseEntity(
response,
OauthTokenResponse.class,
oauthTokenResponse -> new LoginTokenPair(
new BearerToken(oauthTokenResponse.access_token, oauthTokenResponse.expires_in),
new BearerToken(oauthTokenResponse.refresh_token, refreshTokenValidity)));
}
This saves headers and status code, but error message is lost :/

Posting to HTTPS in Java

I want to post to an url https://host:0101 with a request body. I have done this with non-ssl, http, but having trouble with https.
I have both the trust store (JKS) and keystore (PKCS12)'s full path, and their passwords as properties in a .properties file.
What i have to far is:
public sendPost () throws Exception {
SSLContext sslContext = getContext();
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(factory).build();
HttpPost post = new HttpPost("https://host:0101/post");
StringEntity entity = new StringEntity(jsonData);
post.setEntity(entity);
response=client.execute(request);
responseCode = response.getStatusLine().getStatusCode();
}
public SSLContext getContext() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JKS");
FileInputStream instream = new FileInputStream(new File(PATH_TO_KEYSTORE));
try {
keyStore.load(instream, "password".toCharArray());
}finally {instream.close()}
return SSLContexts.custom().loadTrustMaterial(keyStore).build();
}
I am testing this by running a Junit test just to verify that it works before deploying.
#Test
public void test() throws Exception {
int responseCode = entityUnderTest.sendPost();
assertEquals(200, responseCode);
}
The error is at line: response=client.execute(request); with the HttpHostConnectException
Failed: Connection Refused
Is the service available through HTTPS? The connection refused error occurs when there is some problem with the availability of your service.
This could be caused by a lot of problems:
your service is not running
your service is not available through HTTPS
a proxy/routing error (does your test system reach the host and port?)
If there was a problem with the authentication you would get a different message (eg: SSLPeerUnverifiedException, SSLHandshakeException).
This is working for me.. try it
Here is the post method
public static CookieManager msCookieManager = null;
private boolean isPinBuild=false;
public static String[] post(Context context, String Url, List<NameValuePair> values, List<NameValuePair> header_list)
throws ConnectTimeoutException {
String[] result = { "", "" };
try {
URL url = new URL(Url);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
Log.i("TAG_URL : ", Url);
if (isPinBuild) {
KeyPinStore keystore = KeyPinStore.getInstance(context);
javax.net.ssl.SSLSocketFactory factory = keystore.getContext().getSocketFactory();
SSLSocket socket = (SSLSocket)factory.createSocket();
socket.setEnabledCipherSuites(new String[]{"RC4-MD5", "DES-CBC-SHA", "DES-CBC3-SHA"});
socket.setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
socket.setSoTimeout(60000);
urlConnection.setSSLSocketFactory(factory);
} else {
KeyUnPinStore keystore = KeyUnPinStore.getInstance(context);
javax.net.ssl.SSLSocketFactory factory = keystore.getContext().getSocketFactory();
SSLSocket socket = (SSLSocket)factory.createSocket();
socket.setEnabledCipherSuites(new String[]{"RC4-MD5", "DES-CBC-SHA", "DES-CBC3-SHA"});
socket.setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
socket.setSoTimeout(60000);
urlConnection.setSSLSocketFactory(factory);
}
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER;
urlConnection.setRequestMethod("POST");
urlConnection.setConnectTimeout(connection_timeout);
urlConnection.setHostnameVerifier(hostnameVerifier);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
for (NameValuePair header : header_list) {
urlConnection.addRequestProperty(header.getName(), header.getValue());
}
if (msCookieManager == null) {
msCookieManager = new CookieManager();
}
if (msCookieManager != null && msCookieManager.getCookieStore().getCookies().size() > 0) {
urlConnection.setRequestProperty(COOKIE, TextUtils.join(";", msCookieManager.getCookieStore().getCookies()));
}
Log.i("TAG_POST_COOKIE : ", msCookieManager.getCookieStore().getCookies().toString());
String postData = "";
for (NameValuePair value : values) {
postData = postData + value.getName() + "=" + URLEncoder.encode(value.getValue(), "UTF-8") + "&";
}
if (!TextUtils.isEmpty(postData) && postData.length() > 2) {
postData = postData.substring(0, postData.length() - 1);
}
Log.i("TAG_POSTDATA : ", postData);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setFixedLengthStreamingMode(postData.getBytes().length);
PrintWriter out = new PrintWriter(urlConnection.getOutputStream());
out.print(postData);
out.close();
// always check HTTP response code first
int responseCode = urlConnection.getResponseCode();
result[0] = responseCode + "";
if (responseCode == HttpURLConnection.HTTP_OK) {
// Get Response
InputStream is = urlConnection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while ((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
Map<String, List<String>> headerFields = urlConnection.getHeaderFields();
List<String> cookiesHeader = headerFields.get(COOKIES_HEADER);
/*if (cookiesHeader != null) {
for (String cookie : cookiesHeader) {
if (HttpCookie.parse(cookie).get(0).toString().contains(JSESSIONID)) {
msCookieManager.getCookieStore().add(null, HttpCookie.parse(cookie).get(0));
}
}
}*/
if (!TextUtils.isEmpty(response)) {
result[0] = HttpConnectionUrl.RESPONSECODE_REQUESTSUCCESS;
result[1] = response.toString();
Log.i("TAG_RESPONSE : ", result[1]);
}
}
} catch (UnsupportedEncodingException e) {
result[0] = HttpConnectionUrl.RESPONSECODE_CONNECTIONTIMEOUT;
e.printStackTrace();
} catch (ClientProtocolException e) {
result[0] = HttpConnectionUrl.RESPONSECODE_CONNECTIONTIMEOUT;
e.printStackTrace();
} catch (ConnectTimeoutException e) {
result[0] = HttpConnectionUrl.RESPONSECODE_CONNECTIONTIMEOUT;
e.printStackTrace();
} catch (IOException e) {
result[0] = HttpConnectionUrl.RESPONSECODE_CONNECTIONTIMEOUT;
e.printStackTrace();
} catch (Exception e) {
result[0] = HttpConnectionUrl.RESPONSECODE_CONNECTIONTIMEOUT;
e.printStackTrace();
}
return result;
}
here isPinBuild variable use for you have any certificate file or not. If you use any trusted certificate then set true.
Here the KeyPinStore class
public class KeyPinStore {
private static Context context = null;
private static KeyPinStore instance = null;
private SSLContext sslContext = SSLContext.getInstance("SSLv3");
//private SSLContext sslContext = SSLContext.getInstance("TLS");
public static synchronized KeyPinStore getInstance(Context mContext) throws CertificateException, IOException, KeyStoreException,
NoSuchAlgorithmException, KeyManagementException {
if (instance == null) {
context = mContext;
instance = new KeyPinStore();
}
return instance;
}
private KeyPinStore() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// randomCA.crt should be in the Assets directory
InputStream caInput = new BufferedInputStream(context.getAssets().open("yourCertificate.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
// SSLContext context = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
}
public SSLContext getContext() {
return sslContext;
}
}
and KeyUnpinStore class
public class KeyUnPinStore {
private static Context context = null;
private static KeyUnPinStore instance = null;
private SSLContext sslContext = SSLContext.getInstance("TLS");
public static synchronized KeyUnPinStore getInstance(Context mContext) throws CertificateException, IOException, KeyStoreException,
NoSuchAlgorithmException, KeyManagementException {
if (instance == null) {
context = mContext;
instance = new KeyUnPinStore();
}
return instance;
}
private KeyUnPinStore() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
// Create all-trusting host name verifier
// Create an SSLContext that uses our TrustManager
// SSLContext context = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
}
public SSLContext getContext() {
return sslContext;
}
}
Why don't you try using httpbin? It's an external resource that's guaranteed to work. For example:
import java.io.IOException;
import java.net.URI;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONValue;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
public class Post {
public static void main(String[] args) throws Exception {
HttpClient client = new DefaultHttpClient();
int result = post(client, new URI("https://httpbin.org/post"), (JSONObject)JSONValue.parse("{'answer':42}"));
System.err.println(result);
}
public static int post(HttpClient httpClient, URI url, JSONObject content) throws IOException {
final HttpPost post = new HttpPost(url);
post.addHeader("Content-type", ContentType.APPLICATION_JSON.getMimeType());
post.setEntity(new StringEntity(content.toString(), ContentType.APPLICATION_JSON));
final HttpResponse response = httpClient.execute(post);
EntityUtils.consumeQuietly(response.getEntity());
return response.getStatusLine().getStatusCode();
}
}
Assuming that you have generated public/private keypairs using java "keytool" and both trustStore & keyStores are configured properly at client-side & in HOST container(in case of Tomcat server.xml).
In Method:"sendPost()", you may like to get SSL-Connection as follows:
try {
String endpoint="https://host:0101/post";
URL url = new URL(endpoint);
HttpsURLConnection conn = getConnection(url, "POST", uname, passwd);
conn.connect();
writeBody(conn);
catch(Exception e) { throw new RuntimeException(e); }
conn.disconnect();
}
catch(Exception e) { throw new RuntimeException(e); }
private void writeBody(HttpsURLConnection conn) {
try {
String pairs = "who=Freddy&what=Avoid Friday nights if possible.";
OutputStream out = conn.getOutputStream();
out.write(pairs.getBytes());
out.flush();
}
catch(Exception e) { throw new RuntimeException(e); }
}

BlackBerry read json string from an URL

I tried to read a json string loading from an URL, but I could not find complete code sample. Can any one provide or point me to a complete client code. I'm newer to BB development.
this is what I have done but still can't get it work please help me.
Thanks!
To read and parse data from an URL you need to implement two routines. First one of them will handle reading data from the specified URL over HTTP connection, and the second one will parse the data.
Check the following application HttpUrlReaderDemoApp, which will first read the data from specified URL and then parse the retrieved data.
URL used to retrieve data: http://codeincloud.tk/json_android_example.php
Sample data format: {"name":"Froyo", "version":"Android 2.2"}
Classes:
HttpUrlReaderDemoApp - UiApplication instance
HttpResponseListener - Interface used to notify other classes about HTTP request status
HttpUrlReader - Reads the data from given url
AppMainScreen - MainScreen instance
DataParser - Parse data
DataModel - Data definition
Screenshots:
Request for data
When data retrieved successfully
Parsed data
Implementation:
HttpUrlReaderDemoApp
public class HttpUrlReaderDemoApp extends UiApplication {
public static void main(String[] args) {
HttpUrlReaderDemoApp theApp = new HttpUrlReaderDemoApp();
theApp.enterEventDispatcher();
}
public HttpUrlReaderDemoApp() {
pushScreen(new AppMainScreen("HTTP Url Reader Demo Application"));
}
}
HttpResponseListener
public interface HttpResponseListener {
public void onHttpResponseFail(String message, String url);
public void onHttpResponseSuccess(byte bytes[], String url);
}
HttpUrlReader
public class HttpUrlReader implements Runnable {
private String url;
private HttpResponseListener listener;
public HttpUrlReader(String url, HttpResponseListener listener) {
this.url = url;
this.listener = listener;
}
private String getConncetionDependentUrlSuffix() {
// Not implemented
return "";
}
private void notifySuccess(byte bytes[], String url) {
if (listener != null) {
listener.onHttpResponseSuccess(bytes, url);
}
}
private void notifyFailure(String message, String url) {
if (listener != null) {
listener.onHttpResponseFail(message, url);
}
}
private boolean isValidUrl(String url) {
return (url != null && url.length() > 0);
}
public void run() {
if (!isValidUrl(url) || listener == null) {
String message = "Invalid parameters.";
message += !isValidUrl(url) ? " Invalid url." : "";
message += (listener == null) ? " Invalid HttpResponseListerner instance."
: "";
notifyFailure(message, url);
return;
}
// update URL depending on connection type
url += DeviceInfo.isSimulator() ? ";deviceside=true"
: getConncetionDependentUrlSuffix();
// Open the connection and retrieve the data
try {
HttpConnection httpConn = (HttpConnection) Connector.open(url);
int status = httpConn.getResponseCode();
if (status == HttpConnection.HTTP_OK) {
InputStream input = httpConn.openInputStream();
byte[] bytes = IOUtilities.streamToBytes(input);
input.close();
notifySuccess(bytes, url);
} else {
notifyFailure("Failed to retrieve data, HTTP response code: "
+ status, url);
return;
}
httpConn.close();
} catch (Exception e) {
notifyFailure("Failed to retrieve data, Exception: ", e.toString());
return;
}
}
}
AppMainScreen
public class AppMainScreen extends MainScreen implements HttpResponseListener {
private final String URL = "http://codeincloud.tk/json_android_example.php";
public AppMainScreen(String title) {
setTitle(title);
}
private MenuItem miReadData = new MenuItem("Read data", 0, 0) {
public void run() {
requestData();
}
};
protected void makeMenu(Menu menu, int instance) {
menu.add(miReadData);
super.makeMenu(menu, instance);
}
public void close() {
super.close();
}
public void showDialog(final String message) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.alert(message);
}
});
}
private void requestData() {
Thread urlReader = new Thread(new HttpUrlReader(URL, this));
urlReader.start();
showDialog("Request for data from\n \"" + URL + "\"\n started.");
}
public void onHttpResponseFail(String message, String url) {
showDialog("Failure Mesage:\n" + message + "\n\nUrl:\n" + url);
}
public void onHttpResponseSuccess(byte bytes[], String url) {
showDialog("Data retrived from:\n" + url + "\n\nData:\n"
+ new String(bytes));
// now parse response
DataModel dataModel = DataParser.getData(bytes);
if (dataModel == null) {
showDialog("Failed to parse data: " + new String(bytes));
} else {
showDialog("Parsed Data:\nName: " + dataModel.getName()
+ "\nVersion: " + dataModel.getVersion());
}
}
}
DataParser
public class DataParser {
private static final String NAME = "name";
private static final String VERSION = "version";
public static DataModel getData(byte data[]) {
String rawData = new String(data);
DataModel dataModel = new DataModel();
try {
JSONObject jsonObj = new JSONObject(rawData);
if (jsonObj.has(NAME)) {
dataModel.setName(jsonObj.getString(NAME));
}
if (jsonObj.has(VERSION)) {
dataModel.setVersion(jsonObj.getString(VERSION));
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return dataModel;
}
}
DataModel
public class DataModel {
private String name;
private String version;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String model) {
this.version = model;
}
}

I copy/pasted code from ArcGIS .NET ashx proxy into my Controller method, now debugging is jumping all over the place (multi-threaded, apparently)

I have a controller that takes in JSON data via HTTP POST. Inside this controller is a call to a method that I copied/pasted from ArcGIS's .NET implementation of the proxy that's needed to connect to ArcGIS's servers. For the sake of the problem I'm having, that part was irrelavent.
Before copying/pasting, the execution flow was line by line. But now, after copying and pasting (and subsequently adding the call to the method), my debugging execution flow is jumping all over the place (because of different threads going on at the same time). I don't know why this is happening- I didn't see anything that had to do with threads in the code I copied and pasted. Could you tell me why this is happening just because of code that I copied/pasted that appears to not have anything to do with multithreading?
Here's my controller code that makes the call to the method I copied/pasted:
[HttpPost]
public void PostPicture(HttpRequestMessage msg)
{
HttpContext context = HttpContext.Current;
ProcessRequest(context);
...
Here's the code I copied and pasted from ArcGIS (I'm sorry, it's very long):
public void ProcessRequest(HttpContext context)
{
HttpResponse response = context.Response;
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(context.Request.Url);
req.Method = context.Request.HttpMethod;
req.ServicePoint.Expect100Continue = false;
// Set body of request for POST requests
if (context.Request.InputStream.Length > 0)
{
byte[] bytes = new byte[context.Request.InputStream.Length];
context.Request.InputStream.Read(bytes, 0, (int)context.Request.InputStream.Length);
req.ContentLength = bytes.Length;
string ctype = context.Request.ContentType;
if (String.IsNullOrEmpty(ctype))
{
req.ContentType = "application/x-www-form-urlencoded";
}
else
{
req.ContentType = ctype;
}
using (Stream outputStream = req.GetRequestStream())
{
outputStream.Write(bytes, 0, bytes.Length);
}
}
// Send the request to the server
System.Net.WebResponse serverResponse = null;
try
{
serverResponse = req.GetResponse();
}
catch (System.Net.WebException webExc)
{
response.StatusCode = 500;
response.StatusDescription = webExc.Status.ToString();
response.Write(webExc.Response);
response.End();
return;
}
// Set up the response to the client
if (serverResponse != null)
{
response.ContentType = serverResponse.ContentType;
using (Stream byteStream = serverResponse.GetResponseStream())
{
// Text response
if (serverResponse.ContentType.Contains("text") ||
serverResponse.ContentType.Contains("json"))
{
using (StreamReader sr = new StreamReader(byteStream))
{
string strResponse = sr.ReadToEnd();
response.Write(strResponse);
}
}
else
{
// Binary response (image, lyr file, other binary file)
BinaryReader br = new BinaryReader(byteStream);
byte[] outb = br.ReadBytes((int)serverResponse.ContentLength);
br.Close();
// Tell client not to cache the image since it's dynamic
response.CacheControl = "no-cache";
// Send the image to the client
// (Note: if large images/files sent, could modify this to send in chunks)
response.OutputStream.Write(outb, 0, outb.Length);
}
serverResponse.Close();
}
}
response.End();
}
public bool IsReusable
{
get
{
return false;
}
}
// Gets the token for a server URL from a configuration file
// TODO: ?modify so can generate a new short-lived token from username/password in the config file
private string getTokenFromConfigFile(string uri)
{
try
{
ProxyConfig config = ProxyConfig.GetCurrentConfig();
if (config != null)
return config.GetToken(uri);
else
throw new ApplicationException(
"Proxy.config file does not exist at application root, or is not readable.");
}
catch (InvalidOperationException)
{
// Proxy is being used for an unsupported service (proxy.config has mustMatch="true")
HttpResponse response = HttpContext.Current.Response;
response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
response.End();
}
catch (Exception e)
{
if (e is ApplicationException)
throw e;
// just return an empty string at this point
// -- may want to throw an exception, or add to a log file
}
return string.Empty;
}
}
[XmlRoot("ProxyConfig")]
public class ProxyConfig
{
#region Static Members
private static object _lockobject = new object();
public static ProxyConfig LoadProxyConfig(string fileName)
{
ProxyConfig config = null;
lock (_lockobject)
{
if (System.IO.File.Exists(fileName))
{
XmlSerializer reader = new XmlSerializer(typeof(ProxyConfig));
using (System.IO.StreamReader file = new System.IO.StreamReader(fileName))
{
config = (ProxyConfig)reader.Deserialize(file);
}
}
}
return config;
}
public static ProxyConfig GetCurrentConfig()
{
ProxyConfig config = HttpRuntime.Cache["proxyConfig"] as ProxyConfig;
if (config == null)
{
string fileName = GetFilename(HttpContext.Current);
config = LoadProxyConfig(fileName);
if (config != null)
{
CacheDependency dep = new CacheDependency(fileName);
HttpRuntime.Cache.Insert("proxyConfig", config, dep);
}
}
return config;
}
public static string GetFilename(HttpContext context)
{
return context.Server.MapPath("~/proxy.config");
}
#endregion
ServerUrl[] serverUrls;
bool mustMatch;
[XmlArray("serverUrls")]
[XmlArrayItem("serverUrl")]
public ServerUrl[] ServerUrls
{
get { return this.serverUrls; }
set { this.serverUrls = value; }
}
[XmlAttribute("mustMatch")]
public bool MustMatch
{
get { return mustMatch; }
set { mustMatch = value; }
}
public string GetToken(string uri)
{
foreach (ServerUrl su in serverUrls)
{
if (su.MatchAll && uri.StartsWith(su.Url, StringComparison.InvariantCultureIgnoreCase))
{
return su.Token;
}
else
{
if (String.Compare(uri, su.Url, StringComparison.InvariantCultureIgnoreCase) == 0)
return su.Token;
}
}
if (mustMatch)
throw new InvalidOperationException();
return string.Empty;
}
}
public class ServerUrl
{
string url;
bool matchAll;
string token;
[XmlAttribute("url")]
public string Url
{
get { return url; }
set { url = value; }
}
[XmlAttribute("matchAll")]
public bool MatchAll
{
get { return matchAll; }
set { matchAll = value; }
}
[XmlAttribute("token")]
public string Token
{
get { return token; }
set { token = value; }
}
}

Resources