I have a symfony 2 backend and I have installed it on a server. My frontend is an ionic PWA, so it runs in browser and it is also installed on that server, but with an other subdomain. When I send a request from the web app to the backend, I'm getting this error:
OPTIONS https://api.example.com/api/login.json
XMLHttpRequest cannot load https://api.example.com/api/login.json. Response
for preflight has invalid HTTP status code 405
This is my code for the login action:
/**
* Login via username and password or via API key
*
* #Doc\ApiDoc(
* section="Security",
* description="Login",
* views={"default", "security"}
* )
*
* #param ParamFetcher $params
*
* #Rest\RequestParam(name="username", nullable=true, description="Username")
* #Rest\RequestParam(name="password", nullable=true, description="Password")
* #Rest\RequestParam(name="apikey", nullable=true, description="API key (alternative to username + password)")
*
* #Rest\Post("/login", name="api_security_login", options={"method_prefix" = false})
*
* #return Response
*/
public function loginAction(ParamFetcher $params)
{
//...do some stuff here...//
$data = array(
'user' => $userValue,
'apikey' => $user->getApiKey(),
);
$groups = array('default', 'private');
return $this->createAPIResponse(self::STATUS_OK, $data, $groups);
}
This is header from the response:
Access-Control-Allow-Methods:GET,POST,OPTIONS,DELETE,PUT
Access-Control-Allow-Origin:*
Allow:POST
Cache-Control:no-cache
Connection:Keep-Alive
Content-Length:54
Content-Type:application/json
Date:Tue, 29 Aug 2017 08:33:26 GMT
Keep-Alive:timeout=5, max=100
Server:Apache
This is the error message in the prod.log file:
request.ERROR: Uncaught PHP Exception
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException:
"No route found for "OPTIONS /api/login.json": Method Not Allowed
(Allow: POST)" at
/var/www/example.com/api/htdocs/symfony/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php
line 163 {"exception":"[object]
(Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException(code:
0): No route found for \"OPTIONS /api/login.json\": Method Not Allowed
(Allow: POST) at
/var/www/example.com/api/htdocs/symfony/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php:163,
Symfony\Component\Routing\Exception\MethodNotAllowedException(code:
0): at
/var/www/example.com/api/htdocs/symfony/app/cache/prod/appProdUrlMatcher.php:855)"}
[]
So it seems like the OPTIONS request, which is send because CORS, is not allowed, because of the "Allow" header, where only "POST" is allowed. So what is the best way to fix that for all routes?
You only have a route on your action for the POST method:
#Rest\Post("/login", name="api_security_login", options={"method_prefix" = false})
If you want to answer OPTIONS requests, you have to define an Options route using the #Rest\Options annotation.
You can either have several annotations on the same action and test the method inside the action (typically the case with GET and POST for a form action), or define two distinct actions with the same path but different methods.
http://symfony.com/doc/master/bundles/FOSRestBundle/7-manual-route-definition.html
There is no route that accepts the options http method.
No route found for "OPTIONS /api/login.json"
You need to define it first:
/**
* #Rest\Route(
* "/login",
* name="api_security_login",
* methods = {
* Request::METHOD_POST,
* Request::METHOD_OPTIONS,
* }
* )
*/
Clear your cache afterwards and verify that the route is active i.e. using:
bin/console debug:router | grep -i api_security_login
Related
I need to add log after call http request in big project like this?
$response = Http::get('http://example.com');
Log::info(`add request and header and response`);
i want to define global log for all http requests.
i need to define macro like this :
\Illuminate\Support\Facades\Http::macro('log',function(){
Log::info(`add request and header and response`);
});
and call http request like this:
$response = Http::get('http://example.com')->log();
Http is built on Guzzle, which accepts cURL options. One of those is CURLOPT_VERBOSE, rewritten as debug, which will send request data to either the screen or a log file. It accepts a file resource as an option:
$response = Http::withOptions(['debug'=>true])->get('http://example.com');
Or
$fp = fopen(storage_path('http_log.txt'), 'w+');
$response = Http::withOptions(['debug'=>$fp])->get('http://example.com');
If you need more data than that, you can extend the Http class and add your own logging methods to it.
See https://laravel.com/docs/8.x/http-client#guzzle-options and https://docs.guzzlephp.org/en/stable/request-options.html#debug for information on the debug option.
You can use a Terminable Middleware to log the HTTP response after it has already been sent to the browser.
To get the total time you can compare the result of microtime(true) with the laravel constant LARAVEL_START. That constant is defined at bootstrap/autoload.php, the entry point of the framework
For instance, here is a middleware that will log in both HTTP headers and system log the response time. Since you have access to the current request in the $request variable you could leverage that to also log any parameters you want
<?php // File: app/Http/Middleware/MeasureResponseTime.php
namespace App\Http\Middleware;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class MeasureResponseTime
{
/**
* Handle an incoming HTTP request.
*
* #param \Symfony\Component\HttpFoundation\Request $request
* #param \Closure $next
* #return \Symfony\Component\HttpFoundation\Response
*/
public function handle($request, \Closure $next)
{
$response = $next($request);
// Add response time as an HTTP header. For better accuracy ensure this middleware
// is added at the end of the list of global middlewares in the Kernel.php file
if (defined('LARAVEL_START') and $response instanceof Response) {
$response->headers->add(['X-RESPONSE-TIME' => microtime(true) - LARAVEL_START]);
}
return $response;
}
/**
* Perform any final actions for the request lifecycle.
*
* #param \Symfony\Component\HttpFoundation\Request $request
* #param \Symfony\Component\HttpFoundation\Response $response
* #return void
*/
public function terminate($request, $response)
{
// At this point the response has already been sent to the browser so any
// modification to the response (such adding HTTP headers) will have no effect
if (defined('LARAVEL_START') and $request instanceof Request) {
app('log')->debug('Response time', [
'method' => $request->getMethod(),
'uri' => $request->getRequestUri(),
'seconds' => microtime(true) - LARAVEL_START,
]);
}
}
}
I'm trying to build a PHP REST API using Klein as the routing library. Trying to test one of the get routes throw the exception below:
<br />
<b>Fatal error</b>: Uncaught Error: Wrong parameters for Klein\Exceptions\UnhandledException([string
$message [, long $code [, Throwable $previous = NULL]]]) in C:\DA4NA4\Web\task-
scheduler\vendor\klein\klein\src\Klein\Klein.php:954
Stack trace:
#0 C:\DA4NA4\Web\task-scheduler\vendor\klein\klein\src\Klein\Klein.php(954): Exception-
>__construct('SQLSTATE[HY000]...', 'HY000', Object(PDOException))
#1 C:\DA4NA4\Web\task-scheduler\vendor\klein\klein\src\Klein\Klein.php(645): Klein\Klein-
>error(Object(PDOException))
#2 C:\DA4NA4\Web\task-scheduler\public\index.php(20): Klein\Klein->dispatch()
#3 {main}
thrown in <b>C:\DA4NA4\Web\task-scheduler\vendor\klein\klein\src\Klein\Klein.php</b> on line
<b>954</b><br />
My routes file contained the following codes:
<?PHP
require_once '../bootstrap.php';
use Api\Controllers\UserController;
$users = new UserController();
/**
* user routes
*/
//1. get user
$klein->respond('GET', '/api/users/[:username]', function($request){
global $users;
return $users->getUser($request->username);
});
//dispatch routes
$klein->dispatch();
And finally, here's the method from the UserController Class:
public function getUser(string $username){
/**
* process requests for a username
* #param string $username
*/
/**
* set http headers
*/
\header('Access-Control-Allow-Origin:*');
\header('Access-Control-Allow-Methods: GET');
\header('Content-Type: application/json; charset=UTF-8');
\header('Access-Control-Max-Age: 3600');
/**
* sanitize username
*/
$username = \test_input($username);
if(empty($username)){
throw new Exception('Provide username');
}
$user = $this->model->getOne($username);
if($user){
/**
* set http status code
* 200 - successfull
*/
\http_response_code(200);
/**
* encode the user records in json format
* send encode records to user
*/
echo \json_encode(['status'=>true, 'message'=>$user]);
}
}
What I'm possibly doing wrong?
The problem is that exception code is expected to be integer (long in the stacktrace), but PDOException returns strings as exception codes.
There is an open issue about this problem since 2015: https://github.com/klein/klein.php/issues/298
A bigger problem is that you are using library which has been unmaintained for 4 years (since February 2017). My advice is to look for alternative to Klein router.
I'm use L5-Swagger 5.7.* package (wrapper of Swagger-php) and tried describe Laravel REST API. So, my code like this:
/**
* #OA\Post(path="/subscribers",
* #OA\RequestBody(
* #OA\MediaType(
* mediaType="application/json",
* #OA\Schema(
* type="object",
* #OA\Property(property="email", type="string")
* )
* )
* ),
* #OA\Response(response=201,description="Successful created"),
* #OA\Response(response=422, description="Error: Unprocessable Entity")
* )
*/
public function publicStore(SaveSubscriber $request)
{
$subscriber = Subscriber::create($request->all());
return new SubscriberResource($subscriber);
}
But when I try send request via swagger panel I get code:
curl -X POST "https://examile.com/api/subscribers" -H "accept: */*" -H "Content-Type: application/json" -H "X-CSRF-TOKEN: " -d "{\"email\":\"bademail\"}"
As you can see, accept is not application/json and Laravel doesn't identify this as an AJAX request. So, when I send wrong data and expect to get 422 with errors in real I get 200 code with errors in "session". Request (XHR) through the swagger panel is also processed incorrectly, CURL code just for clarity.
Also, I found that in the previous version was used something like:
* #SWG\Post(
* ...
* consumes={"multipart/form-data"},
* produces={"text/plain, application/json"},
* ...)
But now it's already out of date.
So, how get 422 code without redirect if validation fails? Or maybe add 'XMLHttpRequest' header? What is the best thing to do here?
The response(s) didn't specify a mimetype.
#OA\Response(response=201, description="Successful created"),
If you specify a json response, swagger-ui will send an Accept: application/json header.
PS. Because json is so common swagger-php has a #OA\JsonContent shorthand, this works for the response:
#OA\Response(response=201, description="Successful created", #OA\JsonContent()),
and the requestbody:
#OA\RequestBody(
#OA\JsonContent(
type="object",
#OA\Property(property="email", type="string")
)
),
you can use this, i use Request class,
on the file Request
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
public function rules()
{
return [
'currentPassword' => 'required',
'newPassword' => 'required|same:confirmPassword',
'confirmPassword' => 'required'
];
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json([
'errors' => $validator->errors(),
'status' => true
], 422));
}
I use swagger-ui and swagger-json generated with swagger-php. I m not able to do a basic auth to use my endpoint. I m able to get the swagger json file from my application but not to use exposed endpoint. I don't see what i m misunderstanding. If someone could show me a full example with basic auth in swagger 2.0 ?
CORS is enabled and totally working. Swagger-ui is running on localhost:3000 with nodejs and my application is running php with nginx on localhost:80.
I use swagger-ui-dist 3.14.1 that is compatible with swagger 2.0 (swagger-php is 2.0)
3.14.1 | 2018-05-04 | 2.0, 3.0 | tag v3.14.1
I m using theses SWG comments in my controllers to use basicAuth, (server-side)
/**
* #SWG\SecurityScheme(
* securityDefinition="basicAuth",
* name="authorization",
* type="basic",
* scheme="http"
* )
*/
and this comments
/**
* #SWG\Get(
* description="Get all catalog",
* path="/ott/catalogs",
* produces={"application/json"},
* security = {"basicAuth"},
* #SWG\Response(response="200", description="Get all catalogs"),
* #SWG\Response(response="401",description="You are not authorized")
* )
* )
*/
Here is my client-side code:
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://ott/ott/tools/swagger",
host: 'ott',
basePath: 'ott/',
schemes: 'http',
enableCORS: true,
dom_id: '#swagger-ui',
deepLinking: true,
validatorUrl:null,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
requestInterceptor: (req) => {
if (req.loadSpec) {
let hash = btoa("*******" + ":" + "********");
req.headers.Authorization = "Basic " + hash;
}
return req
},
onComplete : () => {
ui.preauthorizeBasic("basicAuth","*******","*******")
}
});
window.ui = ui
When i click to the lock, i have the first error in my console and then when i try to get my catalogs i get a 401 - not authorized because the Basic Authentication header is not sent.
Annotations do not look right. Change #SWG\SecurityScheme to:
/**
* #SWG\SecurityScheme(
* securityDefinition="basicAuth",
* type="basic"
* )
*/
(without the name and scheme attributes), and change the security in #SWG\Get as follows:
/**
* #SWG\Get(
* ...
* security = {{"basicAuth": {}}},
* ...
I`m trying to write some functional tests for a REST API, created using FOS Rest Bundle.
The problem is that when I use the Symfony\Component\BrowserKit, symfony throws me the following error:
{"message":"Unable to find template \"AccountBundle:Account:list.html.twig\". .. }
The code that I run is:
$client = static::createClient();
$client->request('GET','/account');
When I run the request from the browser, it works fine.
Here is the controller:
/**
* Get channel by ID
* #Secure(roles="ROLE_USER")
* #RestView()
* #ApiDoc(
* resource=true,
* description="Get channel by id",
* section="Channel",
* output="Channel"
* )
*/
public function getAction(Channel $channel)
{
return array('channel' => $channel);
}
So when in test scenario, instead of returning the JSON tries to load the template.
You should use the $server parameter of the $client-request() method to set the Accept header to application/json. FOSRestBundle has a listener that returns JSON only if the corresponding Accept header is received, otherwise it will search for the template corresponding to the controller.
$client->request('GET', '/account', array(), array(), array('HTTP_ACCEPT' => 'application/json'));