I am running into a weird issue -> I've got a controller set up to send a POST request to another application using GuzzleHttp - which works fine when this request is started from our VueJs client.
Now I am developing an Artisan Command (per request by the customer) to simplify calling this endpoint like 50+ times (as it generates videos) -> to call this endpoint I am using the following snippet as I am calling an internal controller:
$request = Request::create(
route('videos.new', [], false),
'POST',
[
// Some daata
]
);
$response = app()->handle($request);
$result = json_decode($response->getContent());
But the issue is, now the exact same code in that controller sends a GET request instead of POST to the other application and I cannot figure out why as the method is HARD CODED in there.
I know it's a GET request as I am logging all requests entering the other application at the moment, url etc all looks correct, except that it's a GET request now
Request send with:
$cdnReq = new \GuzzleHttp\Psr7\Request(
'POST',
"/generate/$type?" . http_build_query($query),
[
'Content-Type' => 'application/json'
],
json_encode($input)
);
$this->beforeRequest($cdnReq);
// Step 03: Send request to cdn
Log::debug("Request(" . $cdnReq->getMethod() . ") will be send to: " . $client->getConfig('base_uri') . $cdnReq->getUri(), $input);
$cdnRes = $client->send($cdnReq);
Does anyone have any idea why this is happening?
INFO:
Laravel Version: Laravel/Framework 6.18.32
The issue was related to the URL being called as HTTP and redirected to HTTPS, which changes the POST to GET.
Related
I'm sure this is going to be marked as a duplicate, but I've looked through all the given questions on the same subject and tried many of the suggested solutions, and they haven't worked.
I'm in a laravel project and I have a post request going out through guzzle.
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', $url, [
'headers' => [
'Authorization' => 'Bearer ' . $apiToken,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'allow_redirects' => false,
// 'allow_redirects'=>['strict'=>true]
],
'json' => json_decode($logText, true)
]);
I keep on getting the response back "message": "The GET method is not supported for this route. Supported methods: POST."
I've checked and indeed, it's sending a GET request to the same $url specified above.
I didn't have those allow_redirects settings at first, but both settings were offered up as potential solutions when I googled around. Unfortunately, both options result in the same error message: The GET method is not supported for this route.
Why in the world is my POST request changing to a GET request?
I've also tried $client->post instead, and THAT became a GET request as well.
I've also double checked that the GET error message isn't actually coming from inside the POST request: it's not. The POST route isn't being hit at all.
PHP version 7.2, Laravel version 6.0.2, Guzzle version 6.5.3
Check for redirects on the server, such as HTTP -> HTTPS. Redirects are always a GET request, which will mess up non-GET routing. Using the correct protocol all the way through (such as always use HTTPS) will bypass the redirect.
What I want to do (in the controller)
Receive a request, based on which
Send an external HTTP request
Parse the response, based on which
Return a response
I plan to have multiple functions that all follow this pattern. They're called one by one, in succession.
Symfony 4.3 introduces the HttpClient component and the BrowserKit component which at first sight seem to fit the bill. But as it turns out, things get more complicated. The external HTTP requests are sent to a server that tracks sessions with cookies, which shouldn't be anything too unusual. If the request is sent without cookies, the server always responds with the same landing page content. So we need the cookie. No worries, the response to the initial request comes with a "Set-Cookie" header that contains the cookie that is sent in the "Cookie" header in the subsequent requests. The HTTP status of the response with the cookie is "302 Found" which in a normal web environment, using a web browser, redirects to the next page with the same cookie in the request. Replicating this action in my Symfony controller seems to be very tricky. Expanding on step 2. of what I want to do now:
2.a. Send an external HTTP request
2.b. Read the cookie from the 302 response (without automatic redirection)
2.c. Set the "Cookie" header of the subsequent request, the value coming from 2.b.
2.d. Send the request to the new target (set in the "Location" header of the 302 response)
Here's some of the code from the controller:
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Exception\RedirectionException;
use Symfony\Component\HttpFoundation\JsonResponse;
public function init(Request $request): JsonResponse
{
$url = "external url";
$client = HttpClient::create();
try {
$response = $client->request('GET', $url, [
'max_redirects' => -1
]);
} catch (RedirectionException $redirectionException) {
}
return new JsonResponse([], 200);
}
Not setting max_redirects means that the client follows the redirects and ends up returning the 200 response without any cookies. Setting the value to -1 will throw a redirection
exception and again, the 302 response with the cookie seems to be lost.
I've also tried the HttpBrowser component:
$cookieJar = new CookieJar();
$client = new HttpBrowser(HttpClient::create(), null, $cookieJar);
$client->request('GET', $url);
But I get a 200 response without the cookie as it automatically follows the redirects.
I understand that the components mentioned here are new to Symfony 4.3 and "experimental" but I also don't think my task is overly complicated or unusual. But maybe there are other http clients for Symfony that would be better suited for the task?
As it turns out, this task was easier to complete by trying out other HTTP client components for Symfony than the native ones. So I've put Symfony HttpClient and HttpBrowser aside for now. I managed to implement a solution for this problem with guzzlehttp/guzzle version 6.3.3. Here's what it looks like in my controller function:
use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
public function init(Request $request): JsonResponse {
$client = new Client([
'base_uri' => 'an external domain',
'cookies' => true
]);
// get the 302 response, without following
$response = $client->get('path',[
'allow_redirects' => false
]);
// get the cookie from the 302 response
$jar = new \GuzzleHttp\Cookie\CookieJar(true,[$response->getHeader('Set-Cookie')]);
// follow the redirect manually, with the cookie from the 302 response
$redirectResponse = $client->request('GET', $response->getHeader('Location')[0], [
'cookies' => $jar
]);
$jsonResponse = new JsonResponse(['message' => 'my message'],200);
$originalCookie = Cookie::fromString($response->getHeader('Set-Cookie')[0]);
$newCookie = Cookie::create(
$originalCookie->getName(),
$originalCookie->getValue(),
$originalCookie->getExpiresTime(),
'/',
'my domain',
false,
true,
$originalCookie->isRaw(),
$originalCookie->getSameSite()
);
$jsonResponse->headers->setCookie($newCookie);
return $jsonResponse;
}
The cookie cannot be taken from the response as such or it will never be sent back because the domain or the path doesn't match. So I create a new cookie which is very similar to the original one, then send it back to the browser. This way the cookie comes back in the subsequent requests and the data content can be sent forward.
Other issues that arise here are the same-origin requirements which have to be taken care of on the external server.
I'm still interested in how to do all this with the native Symfony 4.3 components, or also, if anyone can confirm that it's not possible.
My webapp has Laravel as backend framework which provides a Restful API and in the fronend Angularjs is running.
I send different requests through the api and receive the responses and based on the code of response and data included, appropriate messages are shown to user.
Recently when I send requests using PUT method or POST method, when the data has problem in validation process and Laravel should respond with a 422 code in JSON format, instead I receive a text/html response with code 200. and then everything goes wrong.
This does not happen on my local machine, Only when I test the app in production environment this happens.
I also tested UnAuthorized response which is sent with 403 code, and it works flawlessly.
I tested both the automatic validation error for Laravel (as described in documentation: When using the validate method during an AJAX request, Laravel will not generate a redirect response. Instead, Laravel generates a JSON response containing all of the validation errors. This JSON response will be sent with a 422 HTTP status code.) and also using the following method:
return response()->json(compact('errors'),422);
I should mention that I use following methods to send AJAX requests:
function save(data, url) {
return $http({
method: 'POST',
url: url,
headers: {'Content-Type': 'application/json'},
data: angular.toJson(data)
});
}
function update(data, url) {
return $http({
method: 'PUT',
url: url + data.id,
headers: {'Content-Type': 'application/json'},
data: angular.toJson(data)
});
}
needless to say I became totally confused!
UPDATE: It seems to be a problem with Laravel validation process. when the validation runs, request become erroneous. see the following piece of code:
public function altUpdate(Request $request){
$this->authorize('editCustomer', $this->store);
if (!$request->has('customer')){
return response()->json(["message"=>"Problem in received data"],422);
}
$id = $request->customer['id'];
$rules = [
'name' => 'required',
'mobile' => "required|digits:11|unique:customers,mobile,$id,id,store_id,$this->store_id",
'phone' => 'digits_between:8,11',
'email' => "email|max:255|unique:customers,email,$id,id,store_id,$this->store_id",
];
//return response()->json(["problem in data"],422); //this one works properly if uncommented
$validator = Validator::make($request->customer,$rules);
if ($validator->fails()){
$errors = $validator->errors()->all();
Log::info($errors);
return response()->json(["problem in data"],422);//this one is received in client side as a text/html response with code 200
}
$customer = Customer::find($id);
$customer->update(wrapInputs($request->all()));
if ($request->tags) {
$this->syncTags($request->tags, $customer);
}
$message = "Customer updated successfully!";
return response()->json(compact('message'));
}
I still don't know what's the problem of validation process. this code is working on my local machine without any problems but on the production server problem occurs.
I finally got that.
I had added a language file and the file was encoded in UTF-8-BOM, when I converted that file to UTF-8 without BOM things become correct.
the file was resources/lang/[the language]/validation.php and because of the encoding problem the headers were being sent while processing this file.
This question also helped me to find the problem:
Laravel redirect::route is showing a message between page loads
I am trying to send a PUT request method from my Android app to my PHP endpoint but in my endpoint the PUT request is not recognized as a PUT request so I return Request method is wrong! message from my endpoint.
Android interface and request execution
Interface for activation
#PUT("device/activate.php")
Call<DeviceRegistry> registryDevice();
Executing the request
DeviceRegistryAPI registryAPI =
RetrofitController.getRetrofit().create(DeviceRegistryAPI.class);
Call<DeviceRegistry> registryCallback = registryAPI.registryDevice();
response = registryCallback.execute();
With this I am expecting a response but I am getting my endpoint error message.
My PHP endpoint
if($_SERVER['REQUEST_METHOD'] == "PUT"){
//doing something with the data
} else {
$data = array("result" => 0, "message" => "Request method is wrong!");
}
I don't know why the $_SERVER['REQUEST_METHOD'] == "PUT" is false but I wonder if I am missing something on Retrofit 2.
More Info.
I am using Retrofit2.
Update 1: Sending json into the body
I am trying to send a json using the body.
It is my json:
{
"number": 1,
"infoList": [
{
"id": 1,
"info": "something"
},
{
"id": 2,
"info": "something"
}
]
}
There are my classes:
class DataInfo{
public int number;
public List<Info> infoList;
public DataInfo(int number, List<Info> list){
this.number = number;
this.infoList = list;
}
}
class Info{
public int id;
public String info;
}
I changed the PUT interface to this:
#PUT("device/activate.php")
Call<DeviceRegistry> registryDevice(#Body DataInfo info);
But I am getting the same problem.
Update 2: Do I need Header
I have this header in my REstfull client:
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Do I need to put this on my request configuration? How do I do that if I need it?
Update 3: checking the request type of my sending post.
Now I am checking the type of the request. Because I am having the same problem with the PUT/POST requests. So If can solved the problem with the put maybe all the problems will be solved.
When I execute the request and asking and inspect the request it is sending the the type (PUT/POST) but in the server php only detect or GET?? (the below example is using POST and the behavior is the same)
Call<UpdateResponse> requestCall = client.updateMedia(downloadItemList);
Log.i("CCC", requestCall .request().toString());
And the output is a POST:
Request{method=POST, url=http://myserver/api/v1/media/updateMedia.php, tag=null}
so I am sending a POST (no matter if I send a PUT) request to the sever but why in the server I am receiving a GET. I am locked!!! I don't know where is the problem.
Update 4: godaddy hosting.
I have my php server hosting on godaddy. Is there any problem with that? I create a local host and everything works pretty good but the same code is not working on godaddy. I did some research but I didn't find any good answer to this problem so Is possible that godaddy hosting is the problem?
PHP doesn't recognize anything other than GET and POST. the server should throw at you some kind of error like empty request.
To access PUT and other requests use
$putfp = fopen('php://input', 'r'); //will be a JSON string (provided everything got sent)
$putdata = '';
while($data = fread($putfp, filesize('php://input')))
$putdata .= $data;
fclose($putfp);
//php-like variable, if you want
$_PUT = json_decode($putdata);
did not tested, but should work.
I guess the problem is that you don't pass any data along with PUT request, that's why PHP recognizes the request as a GET. So I think you just need to try to pass some data using #FormUrlEncoded, #Multipart or probably #Body annotations
To add header in your retrofit2 you should create an interceptor:
Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException
{
okhttp3.Request.Builder ongoing = chain.request().newBuilder();
ongoing.addHeader("Content-Type", "application/x-www-form-urlencoded");
ongoing.addHeader("Accept", "application/json");
return chain.proceed(ongoing.build());
}
};
and add it to your client builder:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(interceptor);
PHP recognises 'PUT' calls. Extracted from PHP.net:
'REQUEST_METHOD' Which request method was used to access the page;
i.e. 'GET', 'HEAD', 'POST', 'PUT'.
You don't need to send any header if your server isn't expecting any
header.
Prior to use Retrofit or any other networking library, you should check the endpoint using a request http builder, like Postman or Advanced Rest Client. To debug the request/response when running your app or unit tests use a proxy like Charles, it will help you a lot to watch how your request/response really looks.
I have to do a DELETE request, with parameters, in the CodeIgnitor platform. First, I tried using cURL, but I switched to Guzzle.
An example of the request in the console is:
curl -X DELETE -d '{"username":"test"}' http://example.net/resource/id
But in the documentation of Guzzle they use parameters just like GET, like DELETE http://example.net/resource/id?username=test, and I don't want to do that.
I tried with:
$client = new GuzzleHttp\Client();
$client->request('DELETE', $url, $data);
but the request just calls DELETE http://example.com/resource/id without any parameters.
If I interpret your curl request properly, you are attempting to send json data as the body of your delete request.
// turn on debugging mode. This will force guzzle to dump the request and response.
$client = new GuzzleHttp\Client(['debug' => true,]);
// this option will also set the 'Content-Type' header.
$response = $client->delete($uri, [
'json' => $data,
]);
coming late on this question after having same.
Prefered solution, avoiding debug mode is to pass params in 'query' as :
$response = $client->request('DELETE', $uri, ['query' => $datas]);
$datas is an array
Guzzle V6
$response = json_decode($this->client->delete($uri,$params)->getStatusCode());
echo $response;
This will also give the status of the response as 204 or 404