Should I pass my APIKEY in every request I do with Retrofit? - retrofit2

I'd like to know the best way to put my APIKEY in all my REST requests withtout having to add it in parameters of the request.
For now I just have a couple of calls, but I'm trying to see further.
#GET(".")
fun getSearch(#Query("s") text: String, #Query("apikey") APIKEY: String) : Observable<ResponseSearch>
I was wondering if there was a way not to have the APIKEY in variables of every call

You can, but the better solution is to use Okhttp Interceptors
Here's an example:
class TokenInterceptor(private val preferencesStorage: SharedPreferencesStorage) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var original = chain.request()
val token = preferencesStorage.getAccessToken()
val url = original.url().newBuilder().addQueryParameter("apikey", token).build()
original = original.newBuilder().url(url).build()
return chain.proceed(original)
}
}
You should also add TokenInterceptor to your Okhttp client builder
val client = OkHttpClient.Builder()
.addInterceptor(TokenInterceptor(SharedPreferencesStorage()))
.build()

It's better to have a middleware for your requests, where all queries passed through it, and there you will have a single point of adding the APIkey, also i would prefer it to add it to the header instead of query parameters

More "modern" way to achieve it
private fun apiKeyAsQuery(chain: Interceptor.Chain) = chain.proceed(
chain.request()
.newBuilder()
.url(chain.request().url.newBuilder().addQueryParameter("api-key", ApiKey).build())
.build()
)
private fun apiKeyAsHeader(it: Interceptor.Chain) = it.proceed(
it.request()
.newBuilder()
.addHeader("api-key", ApiKey)
.build()
)
...
.client(
OkHttpClient.Builder()
.addInterceptor { apiKeyAsQuery(it) }
// Or
.addInterceptor { apiKeyAsHeader(it) }
.build()
)
...

Related

Httpbuilder put examples for sending multiple query params groovy

I am using Httpbuilder 0.7.1 and groovy 1.8 to invoke the rest call.
I need to send the mutiple values for the same query parameter but it is not working.
I need to send values as below
https:///?action=test&group=grp1&group=grp2
I have tried with the code below and it is working as expected.
Map query = [:]
Map headerMap = [:]
headerMap["Accept"] = 'application/json'
headerMap["Authorization"] = authtoken
def groupsListArray=[]
if (group.contains(",")) {
def groupsList = group.split(",");
for ( singlegroup in groupsList) {
groupsListArray.add(singlegroup.toString())
}
query.put("group",groupListArray)
}
else{
query.put("group",group)
}
def http = new HTTPBuilder(baseUrl)
http.request(method) { req ->
uri.path = path
uri.query = query
headerMap.each { key, value ->
headers."${key}" = "${value}" }
}
Is there any other way, we can send multiple values for the same query parameter with httpbuilder?
Okie, the issue has been identified with the REST service and there is no issue with this code.Thanks for the help

Inject IEnumerable<IInputFormatters>

I've a hard time trying to inject IEnumerable<IInputFormatters> to other services.
I've registered my own InputFromatter and also I've added JsonFormatters. So, at least there should be 3 input formatters, but when I try to inject IEnumerable<IInputFormatters>, I'm constantly getting null (like there is no formatters at all).
My registration looks like:
services.AddMvcCore(config =>
{
config.InputFormatters.Insert(0, new UserContextFormatter());
config.ModelBinderProviders.Insert(0, new ModelBinderProvider());
})
.AddAuthorization()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>())
.AddJsonOptions(opt =>
{
opt.SerializerSettings.Formatting = Formatting.Indented;
opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
opt.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddJsonFormatters()
.AddApiExplorer();
Seems like something easy and stupid, but I'm not good enough to get it. Any ideas?
Thanks!
For IEnumerable<IInputFormatters>, it is not registered as service, so you could not resolve it or access it from Dependency injection.
For InputFormatters or ModelBinderProviders, they are appended to Action<MvcOptions> setupAction, so you could access them from IOptions<MvcOptions>.
Try code below:
public class HomeController : ControllerBase
{
private readonly MvcOptions _options;
public HomeController(IOptions<MvcOptions> options)
{
_options = options.Value;
var inputFormatters = _options.InputFormatters;
var outputFormatters = _options.OutputFormatters;
var modelBinderProviders = _options.ModelBinderProviders;
}

FakeRequest seem to be Null when passed to a controller in unit test

Getting Null Pointer error when unit testing controller. The issue seem to be in the line
def signupUser = Action.async{
implicit request => { //requeust seem to be null
I suspect so because the stacktrace from previous tests point to implicit request line. But I don’t know what could be wrong in this because I am using FakeRequest like so val request = FakeRequest("POST", "/ws/users/signup").withJsonBody(Json.parse("""{"bad": "field"}"""))
Following is a snippet of a controller I want to unit-test
class UserController #Inject()(userRepo: UsersRepository,cc: ControllerComponents, silhouette: Silhouette[JWTEnv])(implicit exec: ExecutionContext) extends AbstractController(cc){
def signupUser = Action.async{
implicit request => {...}
}
I only want to test that the controller returns an error when it gets a request without json body. Thus I don't need Silhouette and I want to mock it. But I am getting null pointer error.
Following is the way I have written my unit test case is
class UserControllerUnitSpec extends PlaySpec with MockitoSugar {
"User signup request with non-JSON body" should {
"return 400 (Bad Request) and the validation text 'Incorrect body type. Body type must be JSON'" in {
val email = "d#d.com"
val loginInfo = LoginInfo(CredentialsProvider.ID, email);
val passwordInfo = PasswordInfo("someHasher","somePassword",Some("someSalt"))
val internalUserProfile = InternalUserProfile(loginInfo,true,Some(passwordInfo))
val externalUserProfile = ExternalUserProfile(email,"d","d",Some("somePassword"))
val userProfile = UserProfile(Some(internalUserProfile),externalUserProfile)
val user = User(UUID.randomUUID(),userProfile)
println("testing with mocked User value",user);
val mockUserRepository = mock[UsersRepository]
when(mockUserRepository.findUser(loginInfo)).thenReturn(Future(Some(user)))
when(mockUserRepository.saveUser(user)).thenReturn(Future(Some(user)))
val mockSilhouette = mock[Silhouette[JWTEnv]] //I am probably not doing this correctly
val mockControllerComponents = mock[ControllerComponents] //I am not sure if this is correct either
val controller = new UserController(mockUserRepository,mockControllerComponents,mockSilhouette)
val result:Future[Result] = controller.signupUser(FakeRequest())
(result.map(response => {
println("response: ",response)
response mustBe BadRequest
}))
}
}
}
Regarding mockControllerComponents, Helpers.stubControllerComponents can be used instead of a mock:
val mockControllerComponents = Helpers.stubControllerComponents()
Regarding mockSilhouette, you have to setup the mock using when(...).thenReturn(...) similarly to how you have done it formockUserRepository, that is, inspect all the usages of silhouette inside signupUser and provide the appropriate method stubs:
val mockSilhouette = mock[Silhouette[JWTEnv]]
when(mockSilhouette.foo(...)).thenReturn(...)
when(mockUserRepository.bar(...)).thenReturn(...)
...
(Posted solution on behalf of the question author).
Here is the answer which worked. Thanks Mario.
class UserControllerUnitSpec extends PlaySpec /*with MockitoSugar*/ {
"User signup request with non-JSON body" should {
"return 400 (Bad Request) and the validation text 'Incorrect body type. Body type must be JSON'" in {
val email = "d#d.com"
val loginInfo = LoginInfo(CredentialsProvider.ID, email);
val passwordInfo = PasswordInfo("someHasher","somePassword",Some("someSalt"))
val internalUserProfile = InternalUserProfile(loginInfo,true,Some(passwordInfo))
val externalUserProfile = ExternalUserProfile(email,"d","d",Some("somePassword"))
val userProfile = UserProfile(Some(internalUserProfile),externalUserProfile)
val user = User(UUID.randomUUID(),userProfile)
println("testing with mocked User value",user);
val mockUserRepository = mock(classOf[UsersRepository])
// when(mockUserRepository.findUser(loginInfo)).thenReturn(Future(Some(user)))
// when(mockUserRepository.saveUser(user)).thenReturn(Future(Some(user)))
// val mockSilhouette = mock(classOf[Silhouette[JWTEnv]])
val mockControllerComponents = Helpers.stubControllerComponents()//mock(classOf[ControllerComponents])
/*
The controller needs Silhouette. Using Silhouette's test kit to create fake instances.
If you would like to test this controller, you must provide an environment that can handle your Identity and Authenticator implementation.
For this case Silhouette provides a FakeEnvironment which automatically sets up all components needed to test your specific actions.
You must only specify one or more LoginInfo -> Identity pairs that should be returned by calling request.identity in your action and
the authenticator instance that tracks this user.
*/
//User extends Identity trait
/*
Under the hood, the environment instantiates a FakeIdentityService which stores your given identities and returns it if needed.
It instantiates also the appropriate AuthenticatorService based on your defined Authenticator type. All Authenticator services are real
service instances set up with their default values and dependencies.
*/
implicit val sys = ActorSystem("MyTest")
implicit val mat = ActorMaterializer()
implicit val env = FakeEnvironment[JWTEnv](Seq(loginInfo->user))
val defaultParser = new mvc.BodyParsers.Default()
val securedAction = new DefaultSecuredAction(new DefaultSecuredRequestHandler(new DefaultSecuredErrorHandler(stubMessagesApi())),defaultParser)
val unsecuredAction = new DefaultUnsecuredAction(new DefaultUnsecuredRequestHandler(new DefaultUnsecuredErrorHandler(stubMessagesApi())),defaultParser)
val userAware = new DefaultUserAwareAction(new DefaultUserAwareRequestHandler(),defaultParser)
val mockSilhouette = new SilhouetteProvider[JWTEnv](env,securedAction,unsecuredAction,userAware)
val controller = new UserController(mockUserRepository,mockControllerComponents,mockSilhouette)
val request = FakeRequest("POST","ws/users/signup")
println("sending request",request)
//val result = controller.someMethod()
val result:Future[Result] = controller.signupUser(request)
status(result) mustBe BAD_REQUEST
}
}
}

Passing path parameters in axios

I am using Axios with NodeJs and trying to pass path parameters in axios.get() method. For example, if URL is url = '/fetch/{date}', I want to replace {date} with the actual date while calling axios.get(url).
I went through the source code on Github and StackOverflow, but couldn't find any method.
Is it possible to keep URLs with parameters as a placeholder and replace them while actually calling the get method of Axios?
Axios doesn't have this feature and it looks like the team don't want to add it.
With credit to previous responders for inspiration, to me this seems like the solution closest to what you (and me) are looking for:
1 - Where you want to store all your URLs and their parameters, define them as functions which use a template string to return the composed URL:
export var fetchDateUrl = (date) => `/fetch/${date}`;
If you need any type-specific formatting of the value being concatenated into the URL, this function is a good place to do it.
2 - Where you want to make the request, call the function with the correct parameters:
import { fetchDateUrl } from 'my-urls';
axios.get(fetchDateUrl(someDateVariable))...;
Another variation, if you really like the idea of naming the parameters at the call site, you can define the URL function to destructure an object like this:
var fetchDateUrl = ({date}) => `/fetch/${date}`;
which you'd then use like this:
axios.get(fetchDateUrl({date: someDateVariable}));
Use template strings
url = `/fetch/${date}`
Or just tag it on
url = '/fetch/'+ date
I think using axios interceptors is better to do this :
//create your instance
const instanceAxios = axios.create({
baseUrl: 'http://localhost:3001'
]);
instanceAxios.interceptors.request.use(config => {
if (!config.url) {
return config;
}
const currentUrl = new URL(config.url, config.baseURL);
// parse pathName to implement variables
Object.entries(config.urlParams || {}).forEach(([
k,
v,
]) => {
currentUrl.pathname = currentUrl.pathname.replace(`:${k}`, encodeURIComponent(v));
});
const authPart = currentUrl.username && currentUrl.password ? `${currentUrl.username}:${currentUrl.password}` : '';
return {
...config,
baseURL: `${currentUrl.protocol}//${authPart}${currentUrl.host}`,
url: currentUrl.pathname,
};
});
// use like :
instanceAxios.get('/issues/:uuid', {
urlParams : {
uuid: '123456789'
}
})
For typescript users, you will need to add this, in one of your .d.ts
declare module 'axios' {
interface AxiosRequestConfig {
urlParams?: Record<string, string>;
}
}
( this is a POC, not really tested, doesn't hesitate if you see something wrong )
You can use template strings ie:
let sellerId = 317737
function getSellerAnalyticsTotals() {
return axios.get(`http://localhost:8000/api/v1/seller/${sellerId}/analytics`);
}
Given some API /fetch/${date} you likely want to wrap your axios call in a function.
const fetchData = (date) => axios.get(`/fetch/${date}`);
fetchData(dateObject.toFormat('yyyy-mm-dd'))
.then(result => { ... });
This requires the calling code to format date correctly however. You can avoid this by using a DateTime library that handles date string parsing and do the format enforcement in the function.
const fetchData = (date) => axios.get(`/fetch/${date.toFormat('yyyy-mm-dd')}`);
fetchData(dateObject)
.then(result => { ... });
you can do like this:
getProduct = (id) => axios.get(`product/${id}`);
I always do it like this:
const res = await axios.get('https://localhost:3000/get', { params: { myParam: 123 } });
I find this to be much clearer than template strings.
More explanation here

How to apply filter on finch endpoint without using finagle filters?

I have more than one endpoints.I am able to apply common filters on endpoints using finagle filter.But now I want to apply a filter on a specific endpoint.
How can I achieve this?
I had a similar question (for basic authentication filtering) that popped up while playing with redbubble's finch template which I partially solved in the following way:
class AuthenticatedEndpoint[A](e: Endpoint[A]) extends Endpoint[A] { self =>
final def apply(mapper: Mapper[A]): Endpoint[mapper.Out] = mapper(self)
final def apply(input: Input): Endpoint.Result[A] =
if (checkSession(input.request)) {
e(input)
} else {
// TODO return something meaningful to the caller (if possible?)
EndpointResult.Skipped
}
}
object AuthenticatedEndpoint {
def validSession[A](e: Endpoint[A]): Endpoint[A] = new AuthenticatedEndpoint(e)
}
(with checkSession returning true if all is well with the request). Then my api is defined as:
val api = "v1" :: loginApi :+: validSession(peopleApi :+: healthApi :+: adminApi)
This works well in the sense that requests without a session won't have access to the endpoints passed to validSession, but I have yet to find an easy way to return an error message to the caller, and I'd be curious to know if I chose the right path here.
This is how I got around it. It's probably not ideal but works.
class AuthenticatedEndpoint[A](e: Endpoint[A])(implicit auth: Request => Boolean) extends Endpoint[A] { self =>
final def apply(mapper: Mapper[A]): Endpoint[mapper.Out] = mapper(self)
final def apply(input: Input): Endpoint.Result[A] =
if (auth(input.request)) {
e(input)
} else {
EndpointResult.Matched[Nothing](input, Rerunnable( Unauthorized(new Exception(s"Authentication Failed."))) )
}
}
object AuthenticatedEndpoint {
def validSession[A](e: Endpoint[A]): Endpoint[A] = new AuthenticatedEndpoint(e)
}

Resources