I am trying to write a middleware that will set additional params to the query string. The use case is to be able to add additional authentication tokens, for eg, to the request as required by the backend, but wanting to inject it independent of the request creation itself.
This is what my middleware(pseudo code) looks like:
class MyMiddleware < Struct.new(:app, :key, :value)
def call(env)
env.params[key] = value #1
#env.params = {key => value} #2
app.call env
end
end
above raises NoMethodError (undefined method[]=' for nil:NilClass)`
above sets the params hash but the parameter is not encoded as part of the query string. The query string remains what it was before the middlewares start processing the request.
My analysis is that since the query string gets built from the params in rack_builder.rb:191
def build_response(connection, request)
app.call(build_env(connection, request))
end
def build_env(connection, request)
Env.new(request.method, request.body,
connection.build_exclusive_url(request.path, request.params), #<== rack_builder.rb:191
request.options, request.headers, connection.ssl,
connection.parallel_manager)
end
Middlewares don't get the opportunity to set additional params. While env has a params property, it is nil and doesn't appear to be touched during or after the middlewares get invoked.
So, I have the following questions:
1. Is there a supported way to achieve this?
2. If not, is there a way to rebuild the query string as part of the middleware executing?
3. Would it be better to defer the URL building to after most of the request middleware chain is executed (but of course, before the adapter gets to do its thing; or do it in the adapter)?
4. Any other options?
Appreciate any guidance.
The answer is here at github: https://github.com/lostisland/faraday/issues/483 by #mislav
Inside the middleware, the request URL is already fully constructed together with all the configured query parameters in a string. The only way to add or remove query parameters is to edit the env.url.query string, e.g.:
MyMiddleware = Struct.new(:app, :token) do
def call(env)
env.url.query = add_query_param(env.url.query, "token", token)
app.call env
end
def add_query_param(query, key, value)
query = query.to_s
query << "&" unless query.empty?
query << "#{Faraday::Utils.escape key}=#{Faraday::Utils.escape value}"
end
end
conn = Faraday.new "http://example.com" do |f|
f.use MyMiddleware, "MYTOKEN"
f.adapter :net_http
end
However, if your middleware is going to be like MyMiddleware above and just add a static query parameter to all requests, the much simpler approach is to avoid the middleware and just configure the Faraday connection instance to apply the same query parameter to all requests:
conn = Faraday.new "http://example.com" do |f|
f.params[:token] = "MYTOKEN"
f.adapter :net_http
end
Related
I have a function where I'm doing a http request to a site that should do an automatic redirect but I'm doing this manually using allow_redirects=False. I have the function itself working something like this:
def make_redirect_request(self):
url = https://some.url.com
response = requests.get(url, headers={"header": "some header"}, status_code=302, allow_redirects=False)
redirected_response = requests.get(response.next.url, headers={"header": "some other header"}, status_code=200)
self.publish(redirected_response.content) # some publishing function for publishing content on page
This works as I would like it to, but I'm struggling to make a unittest that checks this correctly. I have tried something like this:
#unittest.mock.patch("publish")
#unittest.mock.patch("url", "mock_url")
def test_redirect_url(self, mock_publish):
with patch('requests.get', self.session.get) #predefined session with parameters I don't use in this function
mock_response = b"some byte response"
next_url = "mock_redirected_url"
self.adapter.register_uri("GET", "mock_url", status_code=302, allow_redirects=False, next=next_url)
#I want the next parameter to give me the url that I'm being redirected to, which is given by response.next.url in the actual function, but this doesn't work here and it also doesn't understand the allow_redirects parameter
self.adapter.register_uri("GET", next_url, content=mock_response, status_code=200)
self.session.mount("mock", self.adapter)
self.make_redirect_request()
mock_publish.assert_called_once()
I'm not sure how to get the first request to pass the url given by response.next.url to the second request, as the adapter doesn't seem to take the next and allows_redirects arguments.
Trying to implement a custom solution for interacting with TestRail API(http://docs.gurock.com/testrail-api2/accessing), I'm kind of stuck in the following situation:
Api calls are made like this: /index.php?/api/v2/get_case/1, meaning that after anything "?" is a query string param. Is there a way to parametrize this with Retrofit?
If I do something like this:
#GET("index.php?/api/v2/get_case/{id}")
Call<TestCase> getTestCase(#Query("id") int id);
I get this exception:
java.lang.IllegalArgumentException: URL query string "/api/v2/get_case/{id}" must not have replace block. For dynamic query parameters use #Query.
Got that...but how can I proceed further using Retrofit?
Solved this through interceptor
Request currentRequest = chain.request();
String finalURL = currentRequest.url().toString().replace("index.php/", "index.php?/");
Request.Builder request = currentRequest.newBuilder()
.addHeader("Authorization", authToken)
.addHeader("Content-Type", ContentType.JSON.toString())
.url(finalURL);
Illegal Argument exception: Host name may not be null
I am getting this error at last line
HttpResponse httpResponse = httpClient.execute(get)
I tried all possible solutions like encoding url if contains space etc.. and variables like name and phone all these are from my calling class
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String time = sdf.format(cal.getTime());
String value="Dish:"+cr.getString(1)+"Quantity:"+cr.getInt(2)+"Price"+cr.getString(3).trim()+"TotalPrice:"+Integer.parseInt(cr.getString(3))*cr.getInt(2)+"Address:"+address+"CustomerName:"+name+"RestaurantName:"+cr.getString(4).trim();
url = "http:twowaits.in/orderapp.php?name="+name.trim()+"&no="+phone.trim()+"&add="+URLEncoder.encode(address, "UTF-8")+"&rest="+URLEncoder.encode(cr.getString(4),"UTF-8")+"&cost="+cr.getString(3).trim()+"&value="+URLEncoder.encode(value, "UTF-8")+"&dishname="+cr.getString(1).trim()+"&qty="+cr.getInt(2)+"&time="+time;
HttpGet get = new HttpGet(url);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpResponse httpResponse = httpClient.execute(get);
You forgot the // after http:.
url = "http://twowaits.in/......
The stacktrace says it all; your URL does not contain a hostname (a domain name or IP). That means you either didn't supply one, or you made a formatting error somewhere so the URL couldn't be parsed properly. In this case, you did supply a domain name, it's just that the URL isn't formatted properly.
Note that the Apache HttpClient that you are using is deprecated and Google recommends you switch to something else, e.g. URLConnection. Square's OKHttp is also a great alternative.
Also, you might want to try and make your code more readable. Using a builder pattern for your URI would probably help a lot. See URIBuilder or you could just use a StringBuilder.
I have a plone form that basically gets search terms, performs a search, and then directs the user to another form. For this second form, I need to pass a couple variables.
class MySearch(form.SchemaForm):
grok.context(IMyContext)
grok.name('my-search')
ignoreContext = True
schema = ISearchSchema
#button.buttonAndHandler(_(u'Search Method'))
def searchMethod(self, action):
""" group update/removal """
data, errors = self.extractData()
if errors:
self.status = self.formErrorsMessage
return
results = somecall(data['term'])
if results:
self.request.set('myvar',results['myvar'])
self.request.response.redirect('##my-results')
else:
IStatusMessage(self.request).addStatusMessage(_(u"No results found"),"info")
return
This doesn't work - I guess a new request is generated so myvar is immediately lost. I could put this in a query string and include it in the redirect, but would prefer to send it as POST data if possible. I also tried something like
return getMultiAdapter((self.context,self.request),name='my-results')()
to see if I could use that as a starting point to passing in variables, but that just returns me to my-search.
The parameters set on the request object are not taken into account (nor should they) when issuing a redirect.
Append a query string to the redirection URL instead; urllib.urlencode() does the job admirably:
from urllib import urlencode
self.request.response.redirect('##my-results?' + urlencode({'myvar': results['myvar']}))
The .redirect() call returns a 303 See Other response with the URL you passed in the the method as the Location header; the browser then opens that new location, and will include any GET parameters you added to the URL.
I am trying to work with a simple HTTPService. The problem is that my webservice is conscious of the order of arguments it gets. I will tell the problem with an example:
var service:HTTPService = new HTTPService();
var params:Object = new Object();
params.rows = 0;
params.facet = "true";
service.send(params);
Note that in the above code I have mentioned the parameter rows before facet, but the url I recieve is facet=true&rows=0. So I recieve the argument rows before facet and hence my webservice does not work. I figured out that the contents of array is always sent in alphabetical order, which I dont want.
Is there any way I can achieve explict ordering of parameters sent?
Note that I am not in power of changing the logic of webservice(its basically a RPC service supporting both desktop and web client).
Thanks.
I am assuming you are using a get method. Instead of passing params to the HTTPService, build a url string. You can pass get params just by changing that string then calling the service.
service.url = "originalURL" + "?" + "rows=0" + "&" + "facet=true";
service.send();