Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

jsonp support for json format #263

Closed
javiern opened this Issue Jul 20, 2012 · 18 comments

Comments

Projects
None yet
4 participants

javiern commented Jul 20, 2012

Hi, im using this bundle for making an api, and i need to call it from a browser with jquery. but need to use jsonp callbacks...

i made a fast fix for achieve the functionality like this

if ($request->getRequestFormat() && $request->query->get("callback"))
{
     $response->setContent($request->query->get('callback') . "(" . $response->getContent() .")");            
}

in line 122 on ViewResponseListener.php i want to know if there is a better way to achieve this, or if in the future you will add the feture...

if any of you give me some hints in where will be the best place to add this i can make the feature and made a pull request.

my guesses are: or in the listener, or (i know its better) at the the end of createResponse (line 336) in ViewHandler.

and maybe make it configurable....

Owner

lsmith77 commented Jul 20, 2012

relying on a request parameter to define the callback seems like a security risk ..
a quick google search produced this http://tav.espians.com/sanitising-jsonp-callback-identifiers-for-security.html

now as for adding jsonp support the more appropriate place for this imho would be ViewHandler::createResponse(). you could extend this class, check if the format is 'jsonp', inject a parameter into the class instance with a hardcoded callback function (or read and sanitize the callback form the request), call the parent with just 'json' and then adjust the Response accordingly.

as for adding generic support for this, it might be possible to cleanly do this by adding support for this in JMSSerializerBundle.

javiern commented Jul 23, 2012

hi. thanks for your answer... i will research about possibly security risk.
about JMSSerializer, how it can be done? i mean, i dont find a way to add a new format. i have read the docs and doesnt explain it. and looking in the code i have seen the serialization visitors and seems easy to implement one, but im not getting how to make the bundle use my visitor:

i tried defining a service like this

        <parameter key="my_bundle.jsonp_serialization_visitor.class">MyBundle\JsonpSerializationVisitor</parameter>
        <service id="my_bundle.jsonp_serialization_visitor" class="%my_bundle.jsonp_serialization_visitor.class%" public="false">
            <argument type="service" id="jms_serializer.naming_strategy" />
            <argument type="collection" />
            <tag name="jms_serializer.serialization_visitor" format="jsonp" />
        </service>

but it keep saying unsupported format.... im surely missing something...

can u give me a hint? Thanks!

javiern commented Jul 23, 2012

yes! i got it working... typo error!! lol! its better to implent this way... thanks.

@javiern javiern closed this Jul 23, 2012

Owner

lsmith77 commented Jul 23, 2012

would probably be useful to add this to either the FOSRestBundle or JMSSerializerBundle docs

javiern commented Jul 23, 2012

yes its probably a good idea... in fact, it is not so simple to add
new format to jmsserializer...
i dont yet know how to inject the handlers(in a clean way) to my
visitor... so i implemented it decorating the existing json format and
used the dependencies from there.... but there must be a better
way....

2012/7/23 Lukas Kahwe Smith
reply@reply.github.com:

would probably be useful to add this to either the FOSRestBundle or JMSSerializerBundle docs


Reply to this email directly or view it on GitHub:
#263 (comment)

Owner

lsmith77 commented Jul 23, 2012

i was just chatting with @schmittjoh on IRC
what we could do is provide an implementation of a custom handler for jsonp and then potentially a configuration flag to enable "jsonp" support. enabling "jsonp" support would also register the mime type via the mime type listener:
https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/2-the-view-layer.md#custom-handler

in this custom handler you would basically just call the ViewHandler::createResponse method with the format "json" and then adjust the returned Response accordingly. you should also support some regexp to validate the callback string which should probably also be configurable.

maybe that is the easiest solution after all.

javiern commented Jul 24, 2012

yes that could be a good way to go, i must confess that i tried this
way. but, wasnt inspired and things get too complicated. so i dropped
it and started for other paths.

what i basicly did was decorate the json visitor, i made a class and
passed the json vistor as parameter, taked the output for that visitor
and wrapped it whit a hardcoded jsonp callback... that way i dont need
to validate the callback gived to the user...

(i am lazy sometimes... :P). but this approch its not good enough if i
need to make another format in the future, i just cant decorate
existing classes always....

Thanks for the help guys! i will give a try the way you say again to
see if i get it working.

2012/7/23 Lukas Kahwe Smith
reply@reply.github.com:

i was just chatting with @schmittjoh on IRC
what we could do is provide an implementation of a custom handler for jsonp and then potentially a configuration flag to enable "jsonp" support. enabling "jsonp" support would also register the mime type via the mime type listener:
https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/2-the-view-layer.md#custom-handler

in this customer listener you would basically just call the ViewHandler::createResponse method with the format "json" and then adjust the returned Response accordingly. you should also support some regexp to validate the callback string which should probably also be configurable.

maybe that is the easiest solution after all.


Reply to this email directly or view it on GitHub:
#263 (comment)

So, how can I set format 'jsonp' to serializer? I have to use Sencha Touch 2 in my project. I have created a Store with proxy
to get jsonp list contains my objects, but when it is serialized by json, not jsonp, I get 'Uncaught SyntaxError: Unexpected token : '

Owner

lsmith77 commented Aug 2, 2012

see my answer in #263 (comment)

you need to register a custom handler .. that "converts" the formst to "json" and then wraps the callback around the returned content:
https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/View/ViewHandler.php#L214

javiern commented Aug 2, 2012

i think the custom view handler approch wont work if he needs to receive jsonp. if you need to receive jsonp for an external service you will need to do something with the serializer.

what u can do, is decorate the json format visitor. (i decorate it because i dont found the way to inject to this visitor the functionality to manage doctrine objects. its not documented and done in a very specific way. so this approch was faster and works).

i left an example of how to do with a serialization visitor, you will need a desarializator visitor, you can do exactly the same in a nother class.

the desarialization visitor has a decode($str) method, so instead of getResult() you will have a decode method.

in that method, you extract the json string from the containg function, and then, call the decode mehod of the decorated class of serializer bundle, and you are done. its preatty easy. but its not the best method there is out there.

if someone knows how to get the custom handlers array of jmsserialer get injected into visitors, it will be really usefull.

after u made the visitor, just add it as a service like this;

iway_util.jsonp_serialization_visitor:
class: %iway_util.jsonp_serialization_visitor.class%
arguments: [ @jms_serializer.naming_strategy, [] ]
tags: [ { name: jms_serializer.serialization_visitor, format: jsonp } ]

the class will look something like this:

<?php
namespace Iway\UtilBundle\Serializer;

use JMS\SerializerBundle\Metadata\ClassMetadata;
use JMS\SerializerBundle\Metadata\PropertyMetadata;
use JMS\SerializerBundle\Serializer\GraphNavigator;
use JMS\SerializerBundle\Serializer\GenericSerializationVisitor;
use JMS\SerializerBundle\Serializer\VisitorInterface;

class JsonpSerializationVisitor implements VisitorInterface
{

    private $jsonVisitor = null;

    public function getResult()
    {
        $encodedData = $this->getJsonVisitor()->getResult();
        return 'jsonp_callback('. $encodedData .')';
    }


    public function getJsonVisitor()
    {
        return $this->jsonVisitor;
    }


    public function setJsonVisitor(VisitorInterface $jsonVisitor)
    {        
        $this->jsonVisitor = $jsonVisitor;
    }


    function prepare($data)
    {
        return $this->getJsonVisitor()->prepare($data);
    }


    function visitString($data, $type)
    {
        return $this->getJsonVisitor()->visitString($data, $type);
    }


    function visitBoolean($data, $type)
    {
        return $this->getJsonVisitor()->visitBoolean($data, $type);
    }


    function visitDouble($data, $type)
    {
        return $this->getJsonVisitor()->visitDouble($data, $type);
    }


    function visitInteger($data, $type)
    {
        return $this->getJsonVisitor()->visitInteger($data, $type);
    }


    function visitUsingCustomHandler($data, $type, &$visited)
    {
        return $this->getJsonVisitor()->visitUsingCustomHandler($data, $type, $visited);
    }


    function visitArray($data, $type)
    {
        return $this->getJsonVisitor()->visitArray($data, $type);
    }


    function visitTraversable($data, $type)
    {
        return $this->getJsonVisitor()->visitTraversable($data, $type);
    }


    function startVisitingObject(ClassMetadata $metadata, $data, $type)
    {
        return $this->getJsonVisitor()->startVisitingObject($metadata, $data, $type);
    }


    function visitProperty(PropertyMetadata $metadata, $data)
    {
        return $this->getJsonVisitor()->visitProperty($metadata, $data);
    }


    function endVisitingObject(ClassMetadata $metadata, $data, $type)
    {
        return $this->getJsonVisitor()->endVisitingObject($metadata, $data, $type);
    }


    function visitPropertyUsingCustomHandler(PropertyMetadata $metadata, $object)
    {
        return $this->getJsonVisitor()->visitPropertyUsingCustomHandler($metadata, $object);
    }


    function setNavigator(GraphNavigator $navigator)
    {
        return $this->getJsonVisitor()->setNavigator($navigator);
    }


    function getNavigator()
    {
        return $this->getJsonVisitor()->getNavigator();
    }    
}

javiern commented Aug 2, 2012

and of course, you can always in an controller get the serializer, instanciate your visitor at the controller, register in that serializer instance and use it, but will only be available on that controller... or you will need to do this every time you need it... its better to register it as a service....

@lsmith77 lsmith77 referenced this issue Aug 2, 2012

Merged

added a JsonpHandler #266

2 of 2 tasks complete
Owner

lsmith77 commented Aug 2, 2012

could you guys check #266 ?

Thanks, but I extricated myself from the problem in another way. I have used AJAX in Sencha Touch, instead of JSONP.

javiern commented Aug 3, 2012

Its great! Thanks a lot! i will give this a try

but i still think its better to rely on the serializer to add formats,
let say, for example y want atom, or rss, in the view handler, u can
only serialize, but implementing the format inside the serializer,
allows you to desarialize too.

2012/8/3 Maciej Rzepiński
reply@reply.github.com:

Thanks, but I extricated myself from the problem in another way. I have used AJAX in Sencha Touch, instead of JSONP.


Reply to this email directly or view it on GitHub:
#263 (comment)

ilanco commented Aug 8, 2012

@lsmith77 Your patch breaks the call to the service fos_rest.view_handler:

This is the error message I get when calling $this->get('fos_rest.view_handler') from the controller.

You have requested a non-existent service "fos_rest.view_handler"
Owner

lsmith77 commented Aug 9, 2012

@ilanco i assume you are talking about #266? i cannot reproduce your issue :-/
did you try to manually clear your cache?

ilanco commented Aug 9, 2012

@lsmith77 Yes #266 :) Actually I checkout out the jsonp_handler branch, cleared my cache with app/console cache:clear --env=prod --no-debug and app/console cache:clear --env=dev and have the following configuration in config.yml:

fos_rest:
    view:
        formats:
            json: true
            xml: true
            rss: false
        templating_formats:
            html: true
        force_redirects:
            html: true
        failed_validation: HTTP_BAD_REQUEST
        default_engine: twig
        jsonp_handler: ~

I narrowed the problem down to the patch in DependencyInjection/FOSRestExtension.php. I dont have much knowledge of Symfony internals, so I can't really pinpoint the exact problem.

Owner

lsmith77 commented Aug 9, 2012

lets move the discussion to #266 please .. but unfortunately i have no idea what could cause this. please check if the service is defined via app/console container:debug | grep fos_rest.view_handler

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment