This question is specific to a lately strange behavior of the Azure mobile Apps Android sdk. Everything was working fine for weeks. Now, my android client app suddenly can't connect to my web app any more. A Toast says "Error while processing request". In Android Studio debugger, I found the exception inside the SDK file MobileServiceConnection.java.
java.io.IOException: stream was reset: PROTOCOL_ERROR
In Azure Portal, my app shows "Healthy" status, but I can see the HTTP errors. Please help.
Following is my code, which was working fine and now throws error.
// Create the Mobile Service Client instance, using the provided mobile app URL.
try {
mClient = new MobileServiceClient(mMobileBackendUrl, activityContext).withFilter(
new ServiceFilter() {
#Override
public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback nextServiceFilter) {
// Get the request contents
String url = request.getUrl();
String content = request.getContent();
if (url != null) {
Log.d("Request URL:", url);
}
if (content != null) {
Log.d("Request Content:", content);
}
// Execute the next service filter in the chain
ListenableFuture<ServiceFilterResponse> responseFuture = nextServiceFilter.onNext(request);
Futures.addCallback(responseFuture, new FutureCallback<ServiceFilterResponse>() {
#Override
public void onFailure(Throwable exception) {
Log.d("Exception:", exception.getMessage());
}
#Override
public void onSuccess(ServiceFilterResponse response) {
if (response != null && response.getContent() != null) {
Log.d("Response Content:", response.getContent());
}
}
});
return responseFuture;
}
}
);
setAzureClient(mClient);
}catch(MalformedURLException e){
createAndShowDialog(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
}catch(Exception e){
createAndShowDialog("There was an error creating the Mobile Service. "+ e.toString(), "Error");
}
Toast.makeText(context, context.getString(R.string.online_authentication), Toast.LENGTH_SHORT).show();
authenticate();
}
private void authenticate() { // give access only to authenticated users via Google account authentication
HashMap<String, String> parameters = new HashMap<>();
parameters.put("access_type", "offline");//use "Refresh tokens"
//login with the Google provider. This will create a call to onActivityResult() method inside the context Activity, which will then call the onActivityResult() below.
mClient.login(MobileServiceAuthenticationProvider.Google, url_scheme_of_your_app, GOOGLE_LOGIN_REQUEST_CODE, parameters);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// When request completes
if (requestCode == 1) {
try {
MobileServiceActivityResult result = mClient.onActivityResult(data);
if (result.isLoggedIn()) {
Toast.makeText(context, context.getString(R.string.azure_auth_login_success) /*+ " " + mClient.getCurrentUser().getUserId()*/, Toast.LENGTH_SHORT).show();
mUserId = mClient.getCurrentUser().getUserId();
} else {//>>>>THIS IS WHERE I AM GETTING THE ERROR
String errorMessage = result.getErrorMessage();
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show();// Error While processing request (it comes form the MobileServiceConnection.java file inside sdk)
}
}catch(Exception e){
Toast.makeText(context, e.toString(), Toast.LENGTH_LONG).show();
}
}
}
I found the answer myself. The error was due to an Azure App Service HTTP2 connection issue. It has nothing to do with the app code. For anyone facing the same problem, here is the solution.
Go to https://resources.azure.com/
Make sure you are in Read/Write mode by clicking in the option to the left of your name.
From the left column, browse to: https://resources.azure.com/subscriptions/yourSubscriptionId/resourceGroups/yourWebAppResourceGroup/providers/Microsoft.Web/sites/yourWebAppName/config/web
Find and Change the property: "http20Enabled": from true to false by clicking EDIT, Update value to “false” and then clicking in Save or PATCH.
Related
I want to access data on the openrouteservice API - specifically the distance between two given coordinates on the globe - from my Android application.
I have made requests and gotten viable responses from another API that converts two given addresses into their latlong coordinates using the same style of code this request is trying to execute. It works fine, the coordinates arrive and i can further utilize them no problem.
My problem is that i seem to be accessing the API wrongly because if I Log the URL as seen below and copy it from the Debug window into my browser it sends the request, gets a response and shows it in the browser window.
But my application doesn't recieve a response from the API as the onResponse code bit is never executed and the "Fetch done" Log never appears in the actual Debug Log.
The following is my setup of code, which uses Volley to access HTTP Requests and which works fine for other APIs.
#Override
protected String doInBackground(String... strings) {
Log.d("Run =>","Query 3");
String targetKoordURL = null;
String startKoordURL = null;
try {
startKoordURL = startK.getString("lon").concat(",").concat(startK.getString("lat"));
targetKoordURL = targetK.getString("lon").concat(",").concat(targetK.getString("lat"));
} catch (JSONException e) {
e.printStackTrace();
}
String URLfin = "https://api.openrouteservice.org/v2/directions/driving-car?api_key=5b3ce3597851110001cf624823e587e7a80c4c6ab02af6d394585213&start="+startKoordURL+"&end="+targetKoordURL;
Log.d("Debug =>", URLfin);
JsonObjectRequest req = new JsonObjectRequest(Request.Method.GET, URLfin, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
store = response;
Log.d("Run =>", "Fetch done!");
continueImp();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
if(error instanceof TimeoutError || error instanceof NoConnectionError){
sideFetcherHTTPRequestStart replace = new sideFetcherHTTPRequestStart();
replace.execute();
Log.d("VOLLEY_ERROR", "Retrying on Kilometer request");
}
error.printStackTrace();
}
}){
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("Accept", "application/json,application/geo+json,application/gpx+xml,img/png; charset=utf-8");
return params;
}
};
return null;
}
You forget to add the request to request queue, try to do as following:
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
JsonObjectRequest req = new JsonObjectRequest(/*params*/);
//add above request to queue
queue.add(req);
I have got a REST-API in my Java Web Application. This has a method to take orders from a customer's Android app (client) and send (after a bunch of tasks, like price calculating etc.) a response back to the client.
#POST
#Path("order")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public OrderResponse takeOrder(OrderRequest request
) throws IOException {
OrderResponse response = new OrderResponse();
String token = request.getTokenString();
CustomerSession session = sessionPool.getSession(token);
if (session != null) {
OrderHeader order = new OrderHeader();
order.setFkOrderHeaderCustomerID(session.getFkCustomerID());
order.setOrderCreationDate(new Date());
Tasks as getting the session for authentication etc. have to be done synchronously, sure. 'Cause the response for the clients depends on it's success or failure.. So far so good.
At the end of this method the client gets an email about the state of his order request.
Email email = EmailGenerator.createOrderEmail(order);
try {
emailService.send(email);
} catch (MessagingException ex) {
Logger.getLogger(CustomerREST.class.getName()).log(Level.SEVERE, null, ex);
}
response.setStatus(OrderStatusEnum.SUCCESS);
} else {
response.setStatus(OrderStatusEnum.TOKEN_INVALID);
}
return response;
}
This sometimes takes up to a few seconds for which the client has to wait for the response. That hurts.
Is there any way to send the response and do that email stuff in the background?
Thread mailingThread = new Thread() {
#Override
public void run() {
try {
Email email = EmailGenerator.createOrderEmail(order);
emailService.send(email);
} catch (MessagingException | IOException ex) {
Logger.getLogger(CustomerREST.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
mailingThread.start();
Thaks Kyle! This seems to do what I attempted!
Using above service with Xamarin form, I have enabled authentication with OAuth (Microsoft and Google) at server level.
Call from Swagger works fine. However I'm getting 401 error accessing this via the app. This neither works for TableController nor APIController. I'm not using EasyTables. Following is my code.
public async Task<bool> AuthenticateAsync()
{
bool success = false;
try
{
if (user == null)
{
user = await ItemManager.DefaultManager.CurrentClient.LoginAsync(this, MobileServiceAuthenticationProvider.MicrosoftAccount);
Constants.MobileToken = user.MobileServiceAuthenticationToken;
}
success = true;
}
catch (Exception ex)
{
CreateAndShowDialog(ex.Message, "Authentication failed");
}
return success;
}
public async Task<ObservableCollection<Item>> GetItemsAsync(bool syncItems = false)
{
try
{
IEnumerable<Item> items = await itemTable
.ToEnumerableAsync();
return new ObservableCollection<Item>(items);
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine(#"Invalid sync operation: {0}", msioe.Message);
}
catch (Exception e)
{
Debug.WriteLine(#"Sync error: {0}", e.Message);
}
return null;
}
I tried using rest service client, but not sure how to pass the authentication header. As I seen by Swagger, its actually sending via cookie AppServiceAuthSession. How should it be done via Xamarin Forms?
public ItemManager(IRestService service)
{
restService = service;
}
public Task<List<Item>> GetTasksAsync()
{
return restService.RefreshDataAsync();
}
I read that the token we must supply as the 'X-ZUMO-AUTH' is not the access token that provider send back to us; it is the token that the mobile service backend sends back. How we suppose to retrieve this token? And I don't see Swagger sending X-Zumo-Auth header.
Following is my Rest Service initialization :
public RestService()
{
client = new HttpClient(new LoggingHandler(true));
client.MaxResponseContentBufferSize = 256000;
client.DefaultRequestHeaders.Add("x-access_type", "offline");
client.DefaultRequestHeaders.Add("x-zumo-auth", Constants.MobileToken);
client.DefaultRequestHeaders.Add("ZUMO-API-VERSION", "2.0.0");
}
public async Task<List<Item>> RefreshDataAsync()
{
Items = new List<Item>();
var uri = new Uri(string.Format(Constants.RestUrl, string.Empty));
try
{
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
Items = JsonConvert.DeserializeObject<List<Item>>(content);
}
}
catch (Exception ex)
{
Debug.WriteLine(#" ERROR {0}", ex.Message);
}
return Items;
}
EDIT
After enabling the server logging - Azure service is actually throwing 404 error. And this only happens if I enable the custom authorization on the server.
After debugging the code, I notice following difference between authentication handled by both Mobile App vs Swagger :
Mobile App sets the Authentication Type as Federation, but Swagger is setting it correctly as microsoftaccount
And this makes the ID different as well :
I must not be passing the token correctly here.
So what I figured out so far is that I need to pass the header X-ZUMO-AUTH with the current user token to make it work.
And handle this header in the API code to make retrieve user details
//Try to retrieve from header if available
actionContext.Request.Headers.TryGetValues("x-zumo-auth", out auth_token);
if (auth_token !=null)
{
try
{
string urlPath = string.Concat(new Uri(actionContext.Request.RequestUri, actionContext.Request.GetRequestContext().VirtualPathRoot).AbsoluteUri, ".auth/me");
var result = Get<List<AzureUserDetail>>(HttpWebRequest.Create(urlPath), auth_token.FirstOrDefault(), null)?.FirstOrDefault();
userID = result.User_Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Val;
}
catch
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.NotAcceptable);
}
}
private void load(OAuth oauth) {
if (oauth != null) {
System.out.println("file :- "+file);
UploadPhotoTask taskUpload = new UploadPhotoTask(this, new File(myDir,path));
System.out.println("taskUpload :- "+taskUpload);
taskUpload.setOnUploadDone(new UploadPhotoTask.onUploadDone() {
#Override
public void onComplete() {
finish();
}
});
taskUpload.execute(oauth);
}
}
I am getting error on Oauth. I have added Oauth key and secret key but still getting error on Oauth. Please it will be very helpful if give any suggestion.Thanks in advance.
I want to be able to detect when a user signs on to my application using passive acs, so that I can add them to my database if this is the first time using my app. Right now I am subscribing to WSFederationAuthenticationModule.SignedIn but I feel I'm missing something. Mainly I'm not sure the best place to subscribe to the event, I got it to work inside PostAuthenticateRequest but its a bit hacky. Any suggestions?
this code is from global.asax
public override void Init()
{
base.Init();
PostAuthenticateRequest += (s, e) =>
{
try
{
FederatedAuthentication.WSFederationAuthenticationModule.SignedIn -= SignedIn;
}
finally
{
FederatedAuthentication.WSFederationAuthenticationModule.SignedIn += SignedIn;
}
};
}
private void SignedIn(object sender, EventArgs e)
{
//do something
}
EDIT:
For now I'm going to use a flag variable to make sure I only subscribe once to SignedIn. Unless someone has any other suggestions that is :) thanks for the help Sandrino. Here is what I have at the moment.
private static bool isFirstRequest = true;
public override void Init()
{
base.Init();
PostAuthenticateRequest += (s, e) => {
if (isFirstRequest)
{
FederatedAuthentication
.WSFederationAuthenticationModule.SignedIn += SignedIn;
isFirstRequest = false;
}
};
}
private void SignedIn(object sender, EventArgs e)
{
//do something
}
EDIT:
A little more info. This problem happens if I'm using the azure emulator, it probably happens when deployed as well but I haven't tried that. I have tested if I am just not able to debug by trying to write to a text file and no text file was created.
Why do you subscribe to the SignedIn event each time the PostAuthenticateRequest event is raised? You can simple subscribe to it when the application starts (in the Global.asax) and it will be raised for each user that signed in:
public class MvcApplication : System.Web.HttpApplication
{
...
protected void Application_Start()
{
...
FederatedAuthentication.ServiceConfigurationCreated += (s, e) =>
{
FederatedAuthentication.WSFederationAuthenticationModule.SignedIn += new EventHandler(OnUserSignedIn);
};
}
private void OnUserSignedIn(object sender, EventArgs e)
{
// Custom logic here.
}
}
The SignedIn event is the best way to detect a user sign in before the application continues. Take a look at the following diagram. Before redirecting back to a page, the SignedIn event is raised to allow you to detect an user sign in:
Reference: http://msdn.microsoft.com/en-us/library/ee517293.aspx
I created a class that derives from ClaimsAuthenticationManager. There is only one method that you have to override, which is
public virtual IClaimsPrincipal Authenticate(string resourceName, IClaimsPrincipal incomingPrincipal);
In my app, I use this method to check if the user, who has successfully authenticated, is really a user of my app (i.e. they exist in my database). If not, I direct them to a signup page.
My class looks something like this:
public override IClaimsPrincipal Authenticate(string resourceName, IClaimsPrincipal incomingPrincipal)
{
if (incomingPrincipal.Identity.IsAuthenticated)
{
var identity = incomingPrincipal.Identity as IClaimsIdentity;
User user = null;
// Get name identifier and identity provider
var nameIdentifierClaim = identity.Claims.SingleOrDefault(c => c.ClaimType.Equals(ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase));
var identityProviderClaim = identity.Claims.SingleOrDefault(c => c.ClaimType.Equals(CustomClaimTypes.IdentityProviderClaimType, StringComparison.OrdinalIgnoreCase));
if (nameIdentifierClaim == null || identityProviderClaim == null)
{
throw new AuthenticationErrorException("Invalid claims", "The claims provided by your Identity Provider are invalid. Please contact your administrator.");
}
try
{
//checking the database here...
using (var context = new CloudContext())
{
user = (from u in context.Users
where u.IdentityProvider == identityProviderClaim.Value &&
u.NameIdentifier == nameIdentifierClaim.Value &&
!u.Account.PendingDelete
select u).FirstOrDefault();
}
}
catch (System.Data.DataException ex)
{
Console.WriteLine(ex.Message);
if (ex.InnerException != null)
Console.WriteLine(ex.InnerException);
throw;
}
}
return incomingPrincipal;
}
Then, in your web.config, you add a section to the <microsoft.identitymodel> area, as so:
<claimsAuthenticationManager type="CloudAnalyzer.UI.Security.CloudAnalyzerClaimsAuthenticationManager" />
I learned this trick from the sample app located here: Windows Azure Marketplace. Even if you're not going to publish in the Window Azure Marketplace it's a good sample with some helpful code snippets you can use for ACS integration.