Retrofit Authenticator called but not work - retrofit

at the first I am sorry for my poor english.
I am developing an android app with nodejs backend.JWT is used for authorization, so I send access token to the server to authorize my request. access token life is short and expire in 60s. for refreshing the access token the app must to send refresh token to server. for this purpose I am using OkHttp authenticator but authenticator is not work. can any body help me to solve this problem
ServiceGenerator
public class ServiceGenerator {
private static final OkHttpClient httpClient =
new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.build();
private static final Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
public static Retrofit retrofit = builder.build();
public static <S> S createService(Class<S> serviceClass) {
Retrofit retrofit = builder.client(httpClient).build();
return retrofit.create(serviceClass);
}
public static <S> S createServiceWithAuth(Class<S> serviceClass, String accessToken, Context context) {
OkHttpClient newClient = httpClient.newBuilder().addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder builder = request.newBuilder();
if(!accessToken.isEmpty()){
builder.addHeader("x-auth-token", accessToken);
}
request = builder.build();
return chain.proceed((request));
}
}).authenticator(CustomAuthenticator.getInstance(accessToken, context))
.build();
Retrofit newRetrofit = retrofit.newBuilder().client(newClient).baseUrl(API_BASE_URL).addConverterFactory(GsonConverterFactory.create()).build();
return newRetrofit.create(serviceClass);
}
}
CustomAuthenticator
public class CustomAuthenticator implements Authenticator {
private String accessToken;
private static CustomAuthenticator INSTANCE;
Context context;
private CustomAuthenticator(String accessToken, Context context){
this.accessToken = accessToken;
Hawk.init(context).build();
this.context = context;
}
static synchronized CustomAuthenticator getInstance(String accessToken, Context context){
if(INSTANCE == null){
INSTANCE = new CustomAuthenticator(accessToken, context);
}
return INSTANCE;
}
#Nullable
#Override
public Request authenticate(#Nullable Route route, #NotNull Response response) throws IOException {
if(responseCount(response) >= 2){
return null;
}
ApiClient client = ServiceGenerator.createService(ApiClient.class);
User user = new User();
user.setRefreshToken(Hawk.get("RefreshToken"));
Call<User> refreshAccessTokenCall = client.refresh(user);
retrofit2.Response<User> res = refreshAccessTokenCall.execute();
if(res.isSuccessful()){
assert res.body() != null;
String accessToken = res.body().getAccessToken();
Hawk.put("AccessToken", accessToken);
return response.request().newBuilder().addHeader("x-auth-token", res.body().getAccessToken()).build();
}else{
return null;
}
}
private int responseCount(Response response){
int result = 1;
while ((response = response.priorResponse()) != null){
result++;
}
return result;
}
}
and method that POST data to server (in Fragment)
private void saveExpert() {
if (!validate()) {
return;
}
progressBar.setVisibility(View.VISIBLE);
Objects.requireNonNull(getActivity()).getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
String first_and_last_name = firstAndLastName.getText().toString();
String national_code = nationalCode.getText().toString();
String phone_number = phoneNumber.getText().toString();
String home_office_city = homeOfficeCity.getText().toString();
String _address = address.getText().toString();
String _field = field.getText().toString();
String sub_field = subField.getText().toString();
String _grade = grade.getText().toString();
ApiClient client = ServiceGenerator.createServiceWithAuth(ApiClient.class, Hawk.get("AccessToken"), getContext());
SetExpert setExpert = new SetExpert(first_and_last_name, national_code, phone_number, home_office_city, _address, _field, sub_field, _grade);
Call<Expert> expertCall = client.saveExpert(setExpert);
expertCall.enqueue(new Callback<Expert>() {
#Override
public void onResponse(#NotNull Call<Expert> call, #NotNull Response<Expert> response) {
if (response.isSuccessful()) {
Expert expert = response.body();
if (expert != null) {
Hawk.init(Objects.requireNonNull(getContext())).build();
Hawk.put("ExpertNationalCode", expert.getNational_code());
progressBar.setVisibility(View.GONE);
Objects.requireNonNull(getActivity()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
new MaterialDialog.Builder(Objects.requireNonNull(getContext()))
.typeface("IRANSansWebMedium.ttf", "IRANSansWeb.ttf")
.title("پیام سیستم")
.content(expert.getFirst_and_last_name() + " عزیز با تشکر از ثبت نام شما. این ثبت نام به منزله تایید نهایی نبوده و پس از بررسی مشخصات و مدارک، با شما تماس گرفته خواهد شد.")
.positiveText("تایید.")
.canceledOnTouchOutside(false)
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(#NonNull MaterialDialog dialog, #NonNull DialogAction which) {
dismiss();
}
})
.show();
}
} else {
ApiError apiError = ErrorUtils.parseError(response);
progressBar.setVisibility(View.GONE);
Objects.requireNonNull(getActivity()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
Toast.makeText(getContext(), apiError.getMessage(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(#NotNull Call<Expert> call, #NotNull Throwable t) {
progressBar.setVisibility(View.GONE);
Objects.requireNonNull(getActivity()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}

removeHeader("x-auth-token") may solve this.
return response.request().newBuilder()
.removeHeader("x-auth-token") // Remove old header
.addHeader("x-auth-token", res.body().getAccessToken())
.build();
To prevent old "x-auth-token" from being sent, removeHeader() seems to be needed.

Related

Android Studio Firebase Cloud Messaging Not Working?

I am developing an e-commerce application in which when user orders places an order, the seller should receive a notification of new order. I am able to send notifications as it shows a toast message of "Response" but no notification is received on the seller part. I checked the code multiple times but I am still unable to find out where I am making the mistake. Here is my code
public class MyFirebaseMessaging extends FirebaseMessagingService {
private static final String NOTIFICATION_CHANNEL_ID = "MY_NOTIFICATION_CHANNEL_ID";
FirebaseAuth firebaseAuth;
FirebaseUser firebaseUser;
#Override
public void onMessageReceived(#NonNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
firebaseAuth = FirebaseAuth.getInstance();
firebaseUser = firebaseAuth.getCurrentUser();
String notificationType = remoteMessage.getData().get("notificationType");
if (notificationType.equals("NewOrder")){
String buyerUid = remoteMessage.getData().get("buyerUid");
String sellerUid = remoteMessage.getData().get("sellerUid");
String orderId = remoteMessage.getData().get("orderId");
String notificationTitle = remoteMessage.getData().get("notificationTitle");
String notificationMessage = remoteMessage.getData().get("notificationMessage");
if (firebaseUser != null && firebaseAuth.getUid().equals(sellerUid)){
showNotification(orderId,sellerUid,buyerUid,notificationTitle,notificationMessage,notificationType);
}
}
if (notificationType.equals("OrderStatusChanged")){
String buyerUid = remoteMessage.getData().get("buyerUid");
String sellerUid = remoteMessage.getData().get("sellerUid");
String orderId = remoteMessage.getData().get("orderId");
String notificationTitle = remoteMessage.getData().get("notificationTitle");
String notificationMessage = remoteMessage.getData().get("notificationMessage");
if (firebaseUser != null && firebaseAuth.getUid().equals(buyerUid)){
showNotification(orderId,sellerUid,buyerUid,notificationTitle,notificationMessage,notificationType);
}
}
}
private void showNotification(String orderId,String sellerUid,String buyerUid,String notificationTitle,String notificationDescription,String notificationType){
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
int notificationID = new Random().nextInt(3000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
setUpNotificationChannel(notificationManager);
}
Intent intent = null;
if (notificationType.equals("NewOrder")){
intent = new Intent(this,ShopOrderDetails.class);
intent.putExtra("orderId",orderId);
intent.putExtra("orderBy",buyerUid);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
} else if (notificationType.equals("OrderStatusChanged")){
intent = new Intent(this,UserOrderDetailsActivity.class);
intent.putExtra("orderId",orderId);
intent.putExtra("orderTo",sellerUid);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_ONE_SHOT);
Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),R.drawable.indianflag);
//notification sound
Uri notificationSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this,NOTIFICATION_CHANNEL_ID);
notificationBuilder.setSmallIcon(R.drawable.indianflag)
.setLargeIcon(largeIcon)
.setContentTitle(notificationTitle)
.setContentText(notificationDescription)
.setSound(notificationSoundUri)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
//show notification
notificationManager.notify(notificationID,notificationBuilder.build());
}
#RequiresApi(api = Build.VERSION_CODES.O)
private void setUpNotificationChannel(NotificationManager notificationManager) {
CharSequence channelName = "Some sample text";
String channelDescription = "Channel Description Here";
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,channelName,NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setDescription(channelDescription);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
if (notificationManager != null){
notificationManager.createNotificationChannel(notificationChannel);
}
}
}
private void prepareNotification(String orderId){
String NOTIFICATION_TOPIC = "/topics/"+ Constants.FCM_TOPIC;
String NOTIFICATION_TITLE = "New Order "+orderId;
String NOTIFICATION_MESSAGE = "You have a new order";
String NOTIFICATION_TYPE = "NewOrder";
JSONObject notificationJo = new JSONObject();
JSONObject notificationBodyJo = new JSONObject();
Log.d("userId",mAuth.getUid());
Log.d("shopId",shopId);
Log.d("orderId",orderId);
try{
notificationBodyJo.put("notificationType",NOTIFICATION_TYPE);
notificationBodyJo.put("buyerUid",mAuth.getUid());
notificationBodyJo.put("sellerUid",shopId);
notificationBodyJo.put("orderId",orderId);
notificationBodyJo.put("notificationTitle",NOTIFICATION_TITLE);
notificationBodyJo.put("notificationMessage",NOTIFICATION_MESSAGE);
notificationJo.put("to",NOTIFICATION_TOPIC);
notificationJo.put("data",notificationBodyJo);
}catch (Exception e){
Toast.makeText(ProceedToCheckoutActivity.this, ""+e.getMessage(), Toast.LENGTH_SHORT).show();
}
sendFCMNotification(notificationJo,orderId);
}
private void sendFCMNotification(JSONObject notificationJo, String timeStamp1) {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("https://fcm.googleapis.com/fcm/send", notificationJo, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Toast.makeText(ProceedToCheckoutActivity.this, "Response", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(ProceedToCheckoutActivity.this,UserOrderDetailsActivity.class);
intent.putExtra("shopID",shopId);
intent.putExtra("orderId",timeStamp1);
startActivity(intent);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("Error Here",error.toString());
Toast.makeText(ProceedToCheckoutActivity.this, "Error", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(ProceedToCheckoutActivity.this,UserOrderDetailsActivity.class);
intent.putExtra("shopID",shopId);
intent.putExtra("orderId",timeStamp1);
startActivity(intent);
}
}){
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String,String> headers = new HashMap<>();
headers.put("Content-Type","application/json");
headers.put("Authorization","key="+Constants.FCM_KEY);
return headers;
}
};
Volley.newRequestQueue(this).add(jsonObjectRequest);
}
Please use retrofit or volley to send notifications to second user.
Link: sending fcm push notifications using retrofit library in android
Module:app dependencies
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
ApiClient
public class ApiClient {
private static final String BASE_URL = "https://fcm.googleapis.com/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
ApiInterface
public interface ApiInterface {
#Headers({"Authorization: key=" + ConstantKey.SERVER_KEY, "Content-Type:application/json"})
#POST("fcm/send")
Call<ResponseBody> sendNotification(#Body RootModel root);
}
RootModel
public class RootModel {
#SerializedName("to") // "to" changed to token
private String token;
#SerializedName("notification")
private NotificationModel notification;
#SerializedName("data")
private DataModel data;
public RootModel(String token, NotificationModel notification, DataModel data) {
this.token = token;
this.notification = notification;
this.data = data;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public NotificationModel getNotification() {
return notification;
}
public void setNotification(NotificationModel notification) {
this.notification = notification;
}
public DataModel getData() {
return data;
}
public void setData(DataModel data) {
this.data = data;
}
}
NotificationModel
public class NotificationModel {
private String title;
private String body;
public NotificationModel(String title, String body) {
this.title = title;
this.body = body;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
DataModel
public class DataModel {
private String name;
private String age;
public DataModel(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
Send notification by using this method
private void sendNotificationToUser(String token) {
RootModel rootModel = new RootModel(token, new NotificationModel("Title", "Body"), new DataModel("Name", "30"));
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
retrofit2.Call<ResponseBody> responseBodyCall = apiService.sendNotification(rootModel);
responseBodyCall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
Log.d(TAG,"Successfully notification send by using retrofit.");
}
#Override
public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) {
}
});
}

How can i capture record key and value when there is a DeserializationException while consuming a message from kafka topic?

I'm using spring boot 2.1.7.RELEASE and spring-kafka 2.2.8.RELEASE.And I'm using #KafkaListener annotation to create a consumer and I'm using all default settings for the consumer.And I'm using below configuration as specified in the Spring-Kafka documentation.
// other props
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer2.class);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer2.class);
props.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class);
props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, AvroDeserializer.class.getName());
return new DefaultKafkaConsumerFactory<>(props);
Now, I've implemented my custom SeekToCurrentErrorHandler by extending SeekToCurrentErrorHandler as per the below thread but the record value is coming as null and the record key is not in a readable format. Please suggest me how can i get the record key and value?
How to capture the exception and message key when using ErrorHandlingDeserializer2 to handle exceptions during deserialization
Here is my custom SeekToCurrentErrorHandler code
#Component
public class MySeekToCurrentErrorHandler extends SeekToCurrentErrorHandler {
private final MyDeadLetterRecoverer deadLetterRecoverer;
#Autowired
public MySeekToCurrentErrorHandler(MyDeadLetterRecoverer deadLetterRecoverer) {
super(-1);
this.deadLetterRecoverer = deadLetterRecoverer;
}
#Override
public void handle(Exception thrownException, List<ConsumerRecord<?, ?>> data, Consumer<?, ?> consumer, MessageListenerContainer container) {
if (thrownException instanceof DeserializationException) {
//Improve to support multiple records
DeserializationException deserializationException = (DeserializationException) thrownException;
deadLetterRecoverer.accept(data.get(0), deserializationException);
ConsumerRecord<?, ?>. consumerRecord = data.get(0);
sout(consumerRecord.key());
sout(consumerRecord.value());
} else {
//Calling super method to let the 'SeekToCurrentErrorHandler' do what it is actually designed for
super.handle(thrownException, data, consumer, container);
}
}
}
If the key fails deserialization, the original byte[] can be obtained by calling getData() on the exception.
Similarly, if the value fails deserialization, use getData() to get the original data.
The DeadLetterPublishingRecoverer does this (since 2.3).
You can tell which of the key or value failed by calling isKey() on the exception.
EDIT
I was wrong, the key and value are available if the value or key failed.
This is written with Boot 2.3.4:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
SeekToCurrentErrorHandler errorHandler(ProducerFactory<String, String> pf) {
Map<String, Object> configs = new HashMap<>(pf.getConfigurationProperties());
configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);
configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);
ProducerFactory<byte[], byte[]> bytesPF = new DefaultKafkaProducerFactory<>(configs);
KafkaOperations<byte[], byte[]> template = new KafkaTemplate<>(bytesPF);
return new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template),
new FixedBackOff(1000, 5));
}
#KafkaListener(id = "so64597061", topics = "so64597061",
properties = {
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG
+ ":org.springframework.kafka.support.serializer.ErrorHandlingDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG
+ ":org.springframework.kafka.support.serializer.ErrorHandlingDeserializer",
ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS
+ ":com.example.demo.Application$FailSometimesDeserializer",
ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS
+ ":com.example.demo.Application$FailSometimesDeserializer"
})
public void listen(String val, #Header(name = KafkaHeaders.RECEIVED_MESSAGE_KEY) String key) {
System.out.println(key + ":" + val);
}
#KafkaListener(id = "so64597061.dlt", topics = "so64597061.DLT",
properties = {
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG
+ ":org.apache.kafka.common.serialization.ByteArrayDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG
+ ":org.apache.kafka.common.serialization.ByteArrayDeserializer"
})
public void dltListen(byte[] val, #Header(name = KafkaHeaders.RECEIVED_MESSAGE_KEY, required = false) byte[] key) {
String keyStr = key != null ? new String(key) : null;
String valStr = val != null ? new String(val) : null;
System.out.println("DLT:" + keyStr + ":" + valStr);
}
#Bean
public ApplicationRunner runner(KafkaTemplate<String, String> template) {
return args -> {
template.send("so64597061", "foo", "bar");
template.send("so64597061", "fail", "keyFailed");
template.send("so64597061", "valueFailed", "fail");
};
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so64597061").partitions(1).replicas(1).build();
}
#Bean
public NewTopic dlt() {
return TopicBuilder.name("so64597061.DLT").partitions(1).replicas(1).build();
}
public static class FailSometimesDeserializer implements Deserializer<byte[]> {
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
#Override
public byte[] deserialize(String topic, byte[] data) {
return data;
}
#Override
public void close() {
}
#Override
public byte[] deserialize(String topic, Headers headers, byte[] data) {
String string = new String(data);
if ("fail".equals(string)) {
throw new RuntimeException("fail");
}
return data;
}
}
}
spring.kafka.consumer.auto-offset-reset=earliest
foo:bar
DLT:fail:keyFailed
DLT:valueFailed:fail

Adding a header in ActionFilterAttribute

I'm trying to assign a token to my header in the request and when the action has finished to save a new token. Problem is that it assigns it to the HttpContext.Request.Headers and not to the client that is calling the API.
ActionFilter
public class TokenFilter: ActionFilterAttribute {
private static string _token {
get;
set;
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
base.OnActionExecuting(filterContext);
if (_token != null) {
filterContext.HttpContext.Request.Headers.Add("AuthToken", "Token " + _token);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {
base.OnActionExecuted(filterContext);
_token = filterContext.HttpContext.Request.Headers["AuthToken"];
_token = _token.Substring(_token.IndexOf(" "));
_token = _token.Remove(0, 1);
if (Client.DefaultRequestHeaders.Contains("AuthToken"))
}
}
Controller Action
[HttpPost]
[TokenFilter]
public async Task < IActionResult > LogIn(User user) {
try {
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(
Encoding.UTF8.GetBytes(user.Username + ":" + user.Password)));
HttpResponseMessage clientTask = await client.GetAsync("https://localhost:44324/api/Auth/LogIn");
if (clientTask.IsSuccessStatusCode) {
string txtBlock = await clientTask.Content.ReadAsStringAsync();
var tokenObject = JsonConvert.DeserializeObject < SessionAPI > (txtBlock);
client.DefaultRequestHeaders.Authorization = null;
client.DefaultRequestHeaders.Add("AuthToken", "Token " + tokenObject.Token);
return RedirectToAction("Index", "Home");
}
else return View("LogInIndex", user);
}
catch(Exception e) {
throw new Exception("An Error has occured" + e);
}
}
The main idea is to have a token that after LogIn is to be assigned to every request that is sent to the API untill Log Out. I know that i can use cookies but part of the assignment is not to use them. Currently i have the Token just being a static string for testing, but that eventually has to be moved somewhere dynamicly for every User.
EDIT
This is an example method that can be called after a successful Log In
[HttpGet]
[TokenFilter]
public async Task<IActionResult> ListAll()
{
try
{
//client.DefaultRequestHeaders.Add("AuthToken", "Token " + HttpContext.Request.RouteValues["token"]);
HttpResponseMessage clientTask = await client.GetAsync("https://localhost:44324/api/User/ListAll");
if (clientTask.IsSuccessStatusCode)
{
string txtBlock = await clientTask.Content.ReadAsStringAsync();
List<User> users = JsonConvert.DeserializeObject<List<User>>(txtBlock);
client.DefaultRequestHeaders.Authorization = null;
return View("ListAll", users);
}
else
return RedirectToAction("Index", "Home");
}
catch (Exception e)
{
throw new Exception("An Error has occured" + e);
}
}
You don't have to override OnActionExecuting
You can do this as follow
public class TokenFilter: ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext filterContext)
{
if (_token != null)
{
filterContext.Response.Headers.Add("AuthToken", "Token " + _token);
}
}
}

ASP.NET core 2 act as reverse proxy usering rewrite middleware

I'm struggling to make my asp.net core 2 app act like a reverse proxy using URL Rewrite rules.
I have the following in my startup.cs:
var rewriteRules = new RewriteOptions()
.AddRedirectToHttps();
.AddRewrite(#"^POC/(.*)", "http://192.168.7.73:3001/$1", true);
app.UseRewriter(rewriteRules);
The rewrite rule is exactly as it is in my IIS settings (which I'm trying to replace with this method) which works fine.
I'm assuming it has something to do with forwarding the headers maybe? Or maybe I just don't understand how the Rewrite Middleware is supposed to work, if you want the requests to be forwarded instead of just rewritten relative to current hostname.
A reverse proxy can be emulated/implemeted within a middleware :
First the startup class where we add a IUrlRewriter service and the ProxyMiddleware.
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUrlRewriter>(new SingleRegexRewriter(#"^/POC/(.*)", "http://192.168.7.73:3001/$1"));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseRewriter(new RewriteOptions().AddRedirectToHttps());
app.UseMiddleware<ProxyMiddleware>();
}
}
Next we will create a basic implementation of IUrlRewriter. The RewriteUri method must transform the HttpContext into an absolute Uri. Or null if the url should not be redirected in the middleware.
public interface IUrlRewriter
{
Task<Uri> RewriteUri(HttpContext context);
}
public class SingleRegexRewriter : IUrlRewriter
{
private readonly string _pattern;
private readonly string _replacement;
private readonly RegexOptions _options;
public SingleRegexRewriter(string pattern, string replacement)
: this(pattern, replacement, RegexOptions.None) { }
public SingleRegexRewriter(string pattern, string replacement, RegexOptions options)
{
_pattern = pattern ?? throw new ArgumentNullException(nameof(pattern));
_replacement = replacement ?? throw new ArgumentNullException(nameof(pattern));
_options = options;
}
public Task<Uri> RewriteUri(HttpContext context)
{
string url = context.Request.Path + context.Request.QueryString;
var newUri = Regex.Replace(url, _pattern, _replacement);
if (Uri.TryCreate(newUri, UriKind.Absolute, out var targetUri))
{
return Task.FromResult(targetUri);
}
return Task.FromResult((Uri)null);
}
}
And then the Middleware (stolen from an old verison of aspnet proxy repo) and customized. It get the IUrlRewrite service as parameter of Invoke method.
The pipeline is :
Try rewrite url
Create a HttpRequestMessage
Copy Request Header and content
Send the request
Copy response header
Copy response content
done
Et voila
public class ProxyMiddleware
{
private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false,
MaxConnectionsPerServer = int.MaxValue,
UseCookies = false,
});
private const string CDN_HEADER_NAME = "Cache-Control";
private static readonly string[] NotForwardedHttpHeaders = new[] { "Connection", "Host" };
private readonly RequestDelegate _next;
private readonly ILogger<ProxyMiddleware> _logger;
public ProxyMiddleware(
RequestDelegate next,
ILogger<ProxyMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter)
{
var targetUri = await urlRewriter.RewriteUri(context);
if (targetUri != null)
{
var requestMessage = GenerateProxifiedRequest(context, targetUri);
await SendAsync(context, requestMessage);
return;
}
await _next(context);
}
private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage)
{
using (var responseMessage = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
{
context.Response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
context.Response.Headers[header.Key] = header.Value.ToArray();
}
context.Response.Headers.Remove("transfer-encoding");
if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME))
{
context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store");
}
await responseMessage.Content.CopyToAsync(context.Response.Body);
}
}
private static HttpRequestMessage GenerateProxifiedRequest(HttpContext context, Uri targetUri)
{
var requestMessage = new HttpRequestMessage();
CopyRequestContentAndHeaders(context, requestMessage);
requestMessage.RequestUri = targetUri;
requestMessage.Headers.Host = targetUri.Host;
requestMessage.Method = GetMethod(context.Request.Method);
return requestMessage;
}
private static void CopyRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)
{
var requestMethod = context.Request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(context.Request.Body);
requestMessage.Content = streamContent;
}
foreach (var header in context.Request.Headers)
{
if (!NotForwardedHttpHeaders.Contains(header.Key))
{
if (header.Key != "User-Agent")
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
else
{
string userAgent = header.Value.Count > 0 ? (header.Value[0] + " " + context.TraceIdentifier) : string.Empty;
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent);
}
}
}
}
}
private static HttpMethod GetMethod(string method)
{
if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;
if (HttpMethods.IsGet(method)) return HttpMethod.Get;
if (HttpMethods.IsHead(method)) return HttpMethod.Head;
if (HttpMethods.IsOptions(method)) return HttpMethod.Options;
if (HttpMethods.IsPost(method)) return HttpMethod.Post;
if (HttpMethods.IsPut(method)) return HttpMethod.Put;
if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;
return new HttpMethod(method);
}
}
Bonus : some other Rewriter
public class PrefixRewriter : IUrlRewriter
{
private readonly PathString _prefix;
private readonly string _newHost;
public PrefixRewriter(PathString prefix, string newHost)
{
_prefix = prefix;
_newHost = newHost;
}
public Task<Uri> RewriteUri(HttpContext context)
{
if (context.Request.Path.StartsWithSegments(_prefix))
{
var newUri = context.Request.Path.Value.Remove(0, _prefix.Value.Length) + context.Request.QueryString;
var targetUri = new Uri(_newHost + newUri);
return Task.FromResult(targetUri);
}
return Task.FromResult((Uri)null);
}
}
public class MergeRewriter : IUrlRewriter
{
private readonly List<IUrlRewriter> _rewriters = new List<IUrlRewriter>();
public MergeRewriter()
{
}
public MergeRewriter(IEnumerable<IUrlRewriter> rewriters)
{
if (rewriters == null) throw new ArgumentNullException(nameof(rewriters));
_rewriters.AddRange(rewriters);
}
public MergeRewriter Add(IUrlRewriter rewriter)
{
if (rewriter == null) throw new ArgumentNullException(nameof(rewriter));
_rewriters.Add(rewriter);
return this;
}
public async Task<Uri> RewriteUri(HttpContext context)
{
foreach (var rewriter in _rewriters)
{
var targetUri = await rewriter.RewriteUri(context);
if(targetUri != null)
{
return targetUri;
}
}
return null;
}
}
// In Statup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUrlRewriter>(new MergeRewriter()
.Add(new PrefixRewriter("/POC/API", "http://localhost:1234"))
.Add(new SingleRegexRewriter(#"^/POC/(.*)", "http://192.168.7.73:3001/$1")));
}
Edit
I found a project to do same but with way more other feature https://github.com/damianh/ProxyKit as a nuget package

How can I return a response in ASP.NET Core MVC middleware using MVC's content negotiation?

I have some ASP.NET Core MVC middleware to catch unhandled exceptions that I would like to return a response from.
While it is easy to just httpContext.Response.WriteAsync to write a string and e.g. use JsonSerializer to serialise an object to a string, I would like to use the standard serialisation settings and content negotiation so that if I change my default output formatting to XML or a text/xml accept header is sent when I have multiple output formatters configured then XML is returned, as it does if I return an ObjectResult from a controller.
Does anyone know how this can be achieved in middleware?
Here is my code so far which only writes JSON:
public class UnhandledExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly IOutputFormatter _outputFormatter;
private readonly IHttpResponseStreamWriterFactory _streamWriterFactory;
public UnhandledExceptionMiddleware(RequestDelegate next, JsonOutputFormatter outputFormatter, IHttpResponseStreamWriterFactory streamWriterFactory)
{
_next = next;
_outputFormatter = outputFormatter;
_streamWriterFactory = streamWriterFactory;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var error = new ErrorResultModel("Internal Server Error", exception.Message, exception.StackTrace);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await _outputFormatter.WriteAsync(new OutputFormatterWriteContext(context, _streamWriterFactory.CreateWriter, typeof(ErrorResultModel), error));
}
}
where ErrorResultModel is defined as:
public class ErrorResultModel
{
public string ResultMessage { get; };
public string ExceptionMessage { get; };
public string ExceptionStackTrace { get; };
public ErrorResultModel(string resultMessage, string exceptionMessage, string exceptionStackTrace)
{
ResultMessage = resultMessage;
ExceptionMessage = exceptionMessage;
ExceptionStackTrace = exceptionStackTrace;
}
}
This is not possible in ASP.NET Core 2.0 MVC.
This will be possible in 2.1:
public static class HttpContextExtensions
{
private static readonly RouteData EmptyRouteData = new RouteData();
private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor();
public static Task WriteResultAsync<TResult>(this HttpContext context, TResult result)
where TResult : IActionResult
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var executor = context.RequestServices.GetService<IActionResultExecutor<TResult>>();
if (executor == null)
{
throw new InvalidOperationException($"No result executor for '{typeof(TResult).FullName}' has been registered.");
}
var routeData = context.GetRouteData() ?? EmptyRouteData;
var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor);
return executor.ExecuteAsync(actionContext, result);
}
}
public class Program : StartupBase
{
public static Task Main(string[] args)
{
return BuildWebHost(args).RunAsync();
}
public static IWebHost BuildWebHost(string[] args)
{
return new WebHostBuilder().UseStartup<Program>().UseKestrel().Build();
}
public override void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddJsonFormatters();
}
public override void Configure(IApplicationBuilder app)
{
app.Use((ctx, next) =>
{
var model = new Person("Krisian", "Hellang");
var result = new ObjectResult(model);
return ctx.WriteResultAsync(result);
});
}
}
public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}

Resources