I'm trying to use Retrofit with Restful WebService. Everything seems alright, but somehow when I run this code this will always returns this
Method not found. Retrofit 404 Error
Here is my WebServices Code
public function processApi() {
$func = strtolower(trim(str_replace("/","",$_POST['request'])));
if ((int)method_exists($this,$func) > 0) {
$this->$func();
} else {
// If the method not exist with in this class, response would be "Page not found".
$this->response('Method not found',404);
}
}
private function login() {
// Cross validation if the request method is POST else it will return "Not Acceptable" status
if ($this->get_request_method() != "POST") {
// If invalid inputs "Bad Request" status message and reason
$error = array('status' => "0", "msg" => "Bad Request");
$this->response($this->json($error), 406);
}
// Input validations
if (empty($email) and empty($password)) {
$error = array('status' => "0", "msg" => "Invalid Email address or Password");
$this->response($this->json($error), 400);
}
}
public class ObjectPost {
#SerializedName("request")
String request;
#SerializedName("email")
String event_id;
public void setRequest(String request) {
this.request = request;
}
public void setEvent_id(String event_id) {
this.event_id = event_id;
}
}
And here is my Android Request Code
public class RestClient {
public interface ClientInterface {
#POST(Config.LOGIN_URL)
void login(#Body ObjectPost mObject,
Callback<LoginBeans> callback);
}
public static ClientInterface initRestAdapter() {
OkHttpClient client = new OkHttpClient();
return (ClientInterface) new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setClient(new OkClient(client))
.setEndpoint(Config.SERVER_URL)
.build()
.create(ClientInterface.class);
}
}
The value in the
Config.LOGIN_URL
Is most likely incorrect. Please remember that
Config.SERVER_URL
Must contain the base URL address. e.g. http://www.server.com/ (also please note the slashes are important)
Next, what is in your attribute must be only the remainder of the specific method that would be appended on that base url. e.g. if the method you want to call is login, it should be
#POST("/login")
Once again, I am not kidding about the slashes.
Also remember that if a query parameter is sent through as null, retrofit ignores it (you may face this problem later).
If you need any further help, you already have your loglevel set to full, please add the logcat to your question so we can see what is happening.
Your code looks alright.
Basically you need to make sure your backend. Ensure that the controller or whatever is actually right.
Maybe this will be quite applicable to you https://github.com/square/retrofit/issues/789
So, instead of looking at your Android code, it's a good reason to look somewhere else I would say.
Related
I have an android app that needs to make a call to a asp.net core web api server.
I am using RestSharp to make the request.
Here is the code generating the request:
public LoginResponse SignInWithGoogle(string token)
{
//Api request for token
RestRequest request = new RestRequest("login/google", Method.POST);
request.AddJsonBody(new { Token = token });
//request.AddParameter("token", token, ParameterType.GetOrPost);
var response = restClient.Execute<LoginResponse>(request);
if (response.ErrorException != null)
{
throw new Exception("The APi request failed. See inner exception for more details", response.ErrorException);
}
AuthenticationToken = response.Data.token;
restClient.Authenticator = authenticator;
return response.Data;
}
Here is the web api code:
[AllowAnonymous]
[HttpPost]
[Route("google")]
public IActionResult GoogleLogin([FromBody] GoogleLoginDto data)
{
GoogleJsonWebSignature.Payload payload;
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SigningCredentials creds = new SigningCredentials(Global.symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
//Try to validate the Google token.
try
{
payload = GoogleJsonWebSignature.ValidateAsync(data.Token).Result;
}
catch (Exception e)
{
return Unauthorized();
}
...
}
GoogleLoginDto contains one property Token that is public.
The problem is that I get a 404. It seems to me that the JSON in the request is not being serialized to GoogleLoginDto but I can't find out why... I'm guessing because the API can't find the data field and so thinks i'm asking for a route that doesn't exist...
I also tried doing request.AddParameter("token", token, ParameterType.GetOrPost); as you can see, but I get an exception saying that Content-Type can't be null.
I thought about adding the Content-Type header but that seems ridiculous because RestSharp is supposed to determine that automatically...
Can anyone see anything I'm missing here? Thanks.
The API is unable to map the provided URL to a controller action. That is what it is 404 Not Found. Nothing to do with the data. It is the URL.
Given that the desired URL is login/google, ensure that the target controller has the proper routes defined that would allow the request to be mapped to the correct actions.
[Route("login")] // Route prefix
public class LoginController : Controller {
[AllowAnonymous]
[HttpPost("google")] // Matches POST login/google
public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginDto data) {
if(ModelState.IsValid) {
GoogleJsonWebSignature.Payload payload;
var tokenHandler = new JwtSecurityTokenHandler();
var creds = new SigningCredentials(Global.symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
//Try to validate the Google token.
try {
payload = await GoogleJsonWebSignature.ValidateAsync(data.Token);
} catch (Exception e) {
return Unauthorized();
}
return Ok();
}
return BadRequest();
}
}
Ruby 2.2.4
Rails 5.0.0.1
retrofit:1.9.0'
otto:1.3.8'
Hello People,
I am just a beginner in programming and I have the task to create an android app with Android Studio, where you can login and logout as a user. Therefore
I programmed a Token-based authentication in RoR 5 API with Json Web Token (JWT). This tutorial helped me: http://tutorials.pluralsight.com/ruby-ruby-on-rails/token-based-authentication-with-ruby-on-rails-5-api ). After putting the right credentials in curl I finally receive the token: {"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA"}
Now after this works fine I tried to implemend the login-function in android studio, which should interact with my RoR server.
I followed strictly a tutorial (https://www.sitepoint.com/retrofit-a-simple-http-client-for-android-and-java/ )which uses Retrofit for authentication with a php server. However I want to implement this for my RoR server. Nevertheless I tried this code out. I was happy, that the program can communicate with my ruby on rails server. If I login on android app with the right credentials, I see on the console that it will be accepted.
However I dont know how I get a message on android, that the login was succesful plus a welcome message with the name of the user. As I said I use JWT in RoR which gives a long token back. Is there a possibility that retrofit can interpret this token? Or do I need to install JSON Web Token for android? Here is a part of the code in ruby on rails:
app/commands/authenticate_user.rb
class AuthenticateUser
prepend SimpleCommand
def initialize(email, password)
#email = email
#password = password
end
def call
JsonWebToken.encode(user_id: user.id) if user
end
private
attr_accessor :email, :password
def user
user = User.find_by_email(email)
return user if user && user.authenticate(password)
errors.add :user_authentication, 'invalid credentials'
nil
end
end
lib/json_web_token.rb
class JsonWebToken
class << self
def encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def decode(token)
body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
HashWithIndifferentAccess.new body
rescue
nil
end
end
end
And here comes a part of my android app, which code is from the tutorial I followed:
public class Communicator {
private static final String TAG = "Communicator";
private static final String SERVER_URL = "http://127.0.0.1/retrofit";
public void loginPost(String username, String password){
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(SERVER_URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
Interface communicatorInterface = restAdapter.create(Interface.class);
Callback<ServerResponse> callback = new Callback<ServerResponse>() {
#Override
public void success(ServerResponse serverResponse, Response response2) {
if(serverResponse.getResponseCode() == 0){
BusProvider.getInstance().post(produceServerEvent(serverResponse));
}else{
BusProvider.getInstance().post(produceErrorEvent(serverResponse.getResponseCode(), serverResponse.getMessage()));
}
}
#Override
public void failure(RetrofitError error) {
if(error != null ){
Log.e(TAG, error.getMessage());
error.printStackTrace();
}
BusProvider.getInstance().post(produceErrorEvent(-200,error.getMessage()));
}
};
communicatorInterface.postData("login", username, password, callback);
}
public void loginGet(String username, String password){
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(SERVER_URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
Interface communicatorInterface = restAdapter.create(Interface.class);
Callback<ServerResponse> callback = new Callback<ServerResponse>() {
#Override
public void success(ServerResponse serverResponse, Response response2) {
if(serverResponse.getResponseCode() == 0){
BusProvider.getInstance().post(produceServerEvent(serverResponse));
}else{
BusProvider.getInstance().post(produceErrorEvent(serverResponse.getResponseCode(), serverResponse.getMessage()));
}
}
#Override
public void failure(RetrofitError error) {
if(error != null ){
Log.e(TAG, error.getMessage());
error.printStackTrace();
}
BusProvider.getInstance().post(produceErrorEvent(-200,error.getMessage()));
}
};
communicatorInterface.getData("login", username, password, callback);
}
}
This is the Php script from the android tutorial. Maybe I need to transfer this into ruby
<?php
//Post Method here
if(isset($_POST['method']) == 'login'){
$username = $_POST['username'];
$password = $_POST['password'];
if($username == "admin" && $password == "admin"){
$response = array('returned_username' => "-admin-",
'returned_password' => "-admin-",
'message' => "Your credentials are so weak [USING_POST]!",
'response_code' => "1");
echo json_encode($response);
}else{
$response = array('response_code' => "-1",
'message' => "invalid username or password");
echo json_encode($response);
}
}
The json encoding in php includes an array (username, password, message, responsecode). How can I include the same array with JWT on ruby on rails? Any idea? And how I get a message on android, that the login was succesful
In the line JsonWebToken.encode(user_id: user.id) if user, you're passing a hash with a key user_id. You could extend this line to include additional attributes, somewhat like
JsonWebToken.encode(user_id: user.id, name: user.name) if user.
You will then need to include a JWT library in your android app and decode the server response (obtained from Retrofit) and you'll be able to access the attributes you encoded.
There is one way to retrieve the token value, first add those lines to your ServerResponse.java file:
#SerializedName("token")
private String token;
String token
this.token = token;
public String getToken() { return token; }
public void setToken(String token) {
this.token = token;
}
According to what is already there and then go to then main activity and store the token in a variable such as this:
String auth_token = serverEvent.getServerResponse().getToken()
next you can make it private final and use it all over your project.
Hope it helps!
I'm trying to use Retrofit (2.0.0-beta3), but when using an Authenticator to add a token, I can't seem to get the data from the synchronous call. Our logging on the back-end just shows a lot of login attempts, but I can't get the data from the body to actually add to the header.
public static class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Route route, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
UserService userService = createService(UserService.class);
Call<Session> call = userService.emailLogin(new Credentials("handle", "pass"));
// This call is made correctly, as it shows up on the back-end.
Session body = call.execute().body();
// This line is never hit.
Logger.d("Session token: " + body.token);
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header("Auth-Token", body.token)
.build();
}
}
I'm not exactly too sure on why it's not even printing anything out. Any tips on how to solve this issue would be greatly appreciated, thanks for taking the time to help.
These are the sources I've been reading on how to implement Retrofit.
Using Authenticator:
https://stackoverflow.com/a/31624433/3106174
https://github.com/square/okhttp/wiki/Recipes#handling-authentication
Making synchronous calls with Retrofit 2:
https://futurestud.io/blog/retrofit-synchronous-and-asynchronous-requests
I managed to get a decent solution using the TokenAuthenticator and an Interceptor and thought I'd share the idea as it may help some others.
Adding the 'TokenInterceptor' class that handles adding the token to the header is the token exists, and the 'TokenAuthenticator' class handles the case when there is no token, and we need to generate one.
I'm sure there are some better ways to implement this, but it's a good starting point I think.
public static class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate( Route route, Response response) throws IOException {
...
Session body = call.execute().body();
Logger.d("Session token: " + body.token);
// Storing the token somewhere.
session.token = body.token;
...
}
private static class TokenInterceptor implements Interceptor {
#Override
public Response intercept( Chain chain ) throws IOException {
Request originalRequest = chain.request();
// Nothing to add to intercepted request if:
// a) Authorization value is empty because user is not logged in yet
// b) There is already a header with updated Authorization value
if (authorizationTokenIsEmpty() || alreadyHasAuthorizationHeader(originalRequest)) {
return chain.proceed(originalRequest);
}
// Add authorization header with updated authorization value to intercepted request
Request authorisedRequest = originalRequest.newBuilder()
.header("Auth-Token", session.token )
.build();
return chain.proceed(authorisedRequest);
}
}
Source:
http://lgvalle.xyz/2015/07/27/okhttp-authentication/
I have similar authenticator and it works with 2.0.0-beta2.
If you get lots of login attempts from you Authenticator, I suggest make sure that when you make the synchronous call, you are not using Authenticator with that call.
That could end up in loop, if also your "emailLogin" fails.
Also I would recommend adding loggingInterceptor to see all trafic to server: Logging with Retrofit 2
I know it's a late answer but for anyone still wondering how to Add / Refresh token with Retrofit 2 Authenticator, here is a working solution:
Note: preferenceHelper is your Preference Manager class where you set/get your shared preferences.
public class AuthenticationHelper implements Authenticator {
private static final String HEADER_AUTHORIZATION = "Authorization";
private static final int REFRESH_TOKEN_FAIL = 403;
private Context context;
AuthenticationHelper(#ApplicationContext Context context) {
this.context = context;
}
#Override
public Request authenticate(#NonNull Route route, #NonNull Response response) throws IOException {
// We need to have a token in order to refresh it.
String token = preferencesHelper.getAccessToken();
if (token == null)
return null;
synchronized (this) {
String newToken = preferencesHelper.getAccessToken();
if (newToken == null)
return null;
// Check if the request made was previously made as an authenticated request.
if (response.request().header(HEADER_AUTHORIZATION) != null) {
// If the token has changed since the request was made, use the new token.
if (!newToken.equals(token)) {
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + newToken)
.build();
}
JsonObject refreshObject = new JsonObject();
refreshObject.addProperty("refreshToken", preferencesHelper.getRefreshToken());
retrofit2.Response<UserToken> tokenResponse = apiService.refreshToken(refreshObject).execute();
if (tokenResponse.isSuccessful()) {
UserToken userToken = tokenResponse.body();
if (userToken == null)
return null;
preferencesHelper.saveAccessToken(userToken.getToken());
preferencesHelper.saveRefreshToken(userToken.getRefreshToken());
// Retry the request with the new token.
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + userToken.getToken())
.build();
} else {
if (tokenResponse.code() == REFRESH_TOKEN_FAIL) {
logoutUser();
}
}
}
}
return null;
}
private void logoutUser() {
// logout user
}
}
Also note:
preferenceHelper and apiService needs to be provided in some way.
This is not an example that will work for all systems and api's but an example in how adding and refreshing the token should be done using Retrofit 2 Authenticator
I have a simple server rest endpoint running Spring -
#RestController
#RequestMapping("/services")
#Transactional
public class CustomerSignInService {
#Autowired
private CustomerDAO customerDao;
#RequestMapping("/customer/signin")
public Customer customerSignIn(#RequestParam(value = "customer") Customer customer) {
//Some Code Here...
return customer;
}
}
I'm trying to pass a Customer object from my Xamarin Android App using this method -
public JsonValue send(String url, SmartJsonSerializer obj)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(url));
request.ContentType = "application/json";
request.Method = "POST";
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(obj.toJsonString());
}
using (WebResponse response = request.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
return JsonObject.Load(stream);
}
}
}
But i keep getting Bad Request Exception (Http Error 400) and obviously my code at the server side is not triggered.
SmartJsonSerializer uses JSON.NET to serialize the Customer object to string -
using System;
using Newtonsoft.Json;
namespace Shared
{
public class SmartJsonSerializer
{
public string toJson()
{
return JsonConvert.SerializeObject(this);
}
}
}
Any help appreciated,
thnx!
Typically if you are posting a complex object to an api like this, you would write it in the request body. You do appear to be doing this on the android side.
I am not familiar with Spring, but it looks that you are expecting customer as a url parameter - Try replacing #RequestParam with #RequestBody.
I was struggling with it for a while, but apparently the solution may be find in the server side.
If it helps, You can look at this
#RestController
#RequestMapping("/services")
#Transactional
public class SomeService {
#RequestMapping(value = "/user/signin", method = RequestMethod.POST)
#ResponseBody
public AppUser signIn(#RequestBody AppUser appUser) {
appUser.invoke();
return appUser;
}
}
I recently started to use Volley lib from Google for my network requests. One of my requests get error 301 for redirect, so my question is that can volley handle redirect somehow automatically or do I have to handle it manually in parseNetworkError or use some kind of RetryPolicyhere?
Thanks.
Replace your url like that url.replace("http", "https");
for example:
if your url looking like that : "http://graph.facebook......." than
it should be like : "https://graph.facebook......."
it works for me
I fixed it catching the http status 301 or 302, reading redirect url and setting it to request then throwing expection which triggers retry.
Edit: Here are the main keys in volley lib which i modified:
Added method public void setUrl(final String url) for class Request
In class BasicNetwork is added check for redirection after // Handle cache validation, if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY) || statusCode == HttpStatus.SC_MOVED_TEMPORARILY), there I read the redirect url with responseHeaders.get("location"), call setUrl with request object and throw error
Error get's catched and it calls attemptRetryOnException
You also need to have RetryPolicy set for the Request (see DefaultRetryPolicy for this)
If you dont want to modify the Volley lib you can catch the 301 and manually re-send the request.
In your GsonRequest class implement deliverError and create a new Request object with the new Location url from the header and insert that to the request queue.
Something like this:
#Override
public void deliverError(final VolleyError error) {
Log.d(TAG, "deliverError");
final int status = error.networkResponse.statusCode;
// Handle 30x
if(HttpURLConnection.HTTP_MOVED_PERM == status || status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
Log.d(TAG, "Location: " + location);
final GsonRequest<T> request = new GsonRequest<T>(method, location, jsonRequest, this.requestContentType, this.clazz, this.ttl, this.listener, this.errorListener);
// Construct a request clone and change the url to redirect location.
RequestManager.getRequestQueue().add(request);
}
}
This way you can keep updating Volley and not have to worry about things breaking.
Like many others, I was simply confused about why Volley wasn't following redirects automatically. By looking at the source code I found that while Volley will set the redirect URL correctly on its own, it won't actually follow it unless the request's retry policy specifies to "retry" at least once. Inexplicably, the default retry policy sets maxNumRetries to 0. So the fix is to set a retry policy with 1 retry (10s timeout and 1x back-off copied from default):
request.setRetryPolicy(new DefaultRetryPolicy(10000, 1, 1.0f))
For reference, here is the source code:
/**
* Constructs a new retry policy.
* #param initialTimeoutMs The initial timeout for the policy.
* #param maxNumRetries The maximum number of retries.
* #param backoffMultiplier Backoff multiplier for the policy.
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
Alternatively, you can create a custom implementation of RetryPolicy that only "retries" in the event of a 301 or 302.
Hope this helps someone!
End up doing a merge of what most #niko and #slott answered:
// Request impl class
// ...
#Override
public void deliverError(VolleyError error) {
super.deliverError(error);
Log.e(TAG, error.getMessage(), error);
final int status = error.networkResponse.statusCode;
// Handle 30x
if (status == HttpURLConnection.HTTP_MOVED_PERM ||
status == HttpURLConnection.HTTP_MOVED_TEMP ||
status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
if (BuildConfig.DEBUG) {
Log.d(TAG, "Location: " + location);
}
// TODO: create new request with new location
// TODO: enqueue new request
}
}
#Override
public String getUrl() {
String url = super.getUrl();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url; // use http by default
}
return url;
}
It worked well overriding StringRequest methods.
Hope it can help someone.
Volley supports redirection without any patches, no need for a separate fork
Explanation:
Volley internally uses HttpClient which by default follows 301/302 unless specified otherwise
From: http://hc.apache.org/httpcomponents-client-4.2.x/tutorial/html/httpagent.html
ClientPNames.HANDLE_REDIRECTS='http.protocol.handle-redirects': defines whether redirects should be handled automatically. This parameter expects a value of type java.lang.Boolean. If this parameter is not set HttpClient will handle redirects automatically.
ok, im a bit late to the game here, but i've recently been trying to achieve this same aspect, so https://stackoverflow.com/a/17483037/2423312 is the best one, given that you are willing to fork volley and maintain it and the answer here : https://stackoverflow.com/a/27566737/2423312 - I'm not sure how this even worked.This one is spot on though : https://stackoverflow.com/a/28454312/2423312. But its actually adding a new request object to the NetworkDipatcher's queue, so you'll have to notify the caller as well somehow, there is one dirty way where you can do this by not modifying the request object + changing the field "mURL", PLEASE NOTE THAT THIS IS DEPENDENT ON YOUR IMPLEMENTATION OF VOLLEY'S RetryPolicy.java INTERFACE AND HOW YOUR CLASSES EXTENDING Request.java CLASS ARE, here you go : welcome REFLECTION
Class volleyRequestClass = request.getClass().getSuperclass();
Field urlField = volleyRequestClass.getDeclaredField("mUrl");
urlField.setAccessible(true);
urlField.set(request, newRedirectURL);
Personally I'd prefer cloning volley though. Plus looks like volley's example BasicNetwork class was designed to fail at redirects : https://github.com/google/volley/blob/ddfb86659df59e7293df9277da216d73c34aa800/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java#L156 so i guess they arent leaning too much on redirects, feel free to suggest/edit. Always looking for good way..
I am using volley:1.1.1 with https url though the request was having some issue. On digging deeper i found that my request method was getting changed from POST to GET due to redirect (permanent redirect 301). I am using using nginx and in server block i was having a rewrite rule that was causing the issue.
So in short everything seems good with latest version of volley. My utility function here-
public void makePostRequest(String url, JSONObject body, final AjaxCallback ajaxCallback) {
try {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST,
url, body, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.d(LOG, response.toString());
ajaxCallback.onSuccess(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(LOG, error.toString());
ajaxCallback.onError(error);
}
});
singleton.getRequestQueue().add(jsonObjectRequest);
} catch(Exception e) {
Log.d(LOG, "Exception makePostRequest");
e.printStackTrace();
}
}
// separate file
public interface AjaxCallback {
void onSuccess(JSONObject response);
void onError(VolleyError error);
}