Skip to main content
16

ShareMeow

Episode 101 · January 20, 2016

A Ruby microservice to generate Medium.com-style tweet quote images for sharing on social media like Twitter and Facebook

Sharing


Transcripts

Now, over the past few months, you've probably have noticed that a lot of tweets and share links are starting to include these images in them that are actually quotes from a website, so you might find something that you want to share and highlight and send to your followers, that is something that is hard to do on Twitter especially because of the 140 character limit, so they've gotten around this by generating images that look like screenshots from the mobile app, and then they embded them in the tweets, and this is something Medium especially has started doing, which is an awesome way to share around that limitation as well as just capturing the context. For example, I had highlighted these four words, but that entire paragraph might be useful for you if you want to get a context of where that came from. This is interesting and something I wanted to talk about how you could go build this into your application on your own site. I wanted to talk about building this from scratch, but as it turns out, producthunt has cloned this feature from Medium, so they've released their own microservice called ShareMeow. If you know any of the guys there, their character definitely comes across and the naming here, so this is an awesome little service, it's a simple little Sinatra app that you can install and it really doesn't have too many dependencies given what it's doing, so it's giving a basic render, so you render this html and CSS and then you're going to take a screenshot of that basically and save that and return that to the requester which would be your websites share code. If you scroll down to the README section, you can see an example of how producthunt uses that. They actually allow you to share comments in image format, and also, they optionally have this highlight as well, just as Medium does, and this is all a regular CSS and html document, it's just rendered on it's own in the browser, and then converted into a screenshot. This little service supports custom fonts, cachable images, and it also does authentication, so this is something that you don't want to get DDOS's by a bunch of users playing with that. The way that that works is that we encode the parameters, and use that as the authentication mechanism, so you'll pass over these two parameters, the encoded params and the encoded h mac digest, and then that will be used to decode the string, which will be a very long url potentially, and then that will return a jpeg image back to your application. This is a service that you'll need to run separately from your rails application, it's a Sinatra app, as a microservice, so that means you can either deploy this to Heroku or run it on your server on your own domain or subdomain, and then just reference that as the domain when you're asking for an image. You basically just define this along with a secret when you run this service, and then your other rails application can talk to it, and they've built a ruby client to make that easier. Really this just creates the url for you so it takes the template and the options and then goes ahead and embeds the encoded parameters into the url. Really when you use their client for this, you're going to be generating urls, and then when those are accessed, it will talk to the ShareMeow microservice, which will do the image generation and return the image. I've got a simple little rails application here with comments, and we're able to add new comments, and we're able to make this share link actually functional, and that is what we're going to do right now, so let's dive into the code.

I've cloned the ShareMeow repository to my laptop, and let's open up the code to take a look at this. This is a regular old Sinatra app, which I know I haven't talked about much, but Sinatra is effectively just a really lightweight version of a web framework. Something like rails but just super duper simple, and you can add in only the functionality you need, and because this service only generates images, there really isn't much to it. For example, you can see that there's a routes file, in app instead of config, and that just delegates to the Base route section. This is going to say: Let's set all the html views inside the app views folder. We're going to enable static files, set login on and that's really it. There's another route in here that will use the digest authorization which is the encoded params in the hmac digest that we talked about, and this is just going to verify that those are correct, that your application and the service are using the same secret keys, and then it's going to generate that image with the params that you sent, and that's really all the Sinatra app is. It's just going to delegate to the ShareMeow image, which is going to simply look for one of these templates, and the associated CSS file, and then render that. How do you go about running this service?

It's really simple, it's a Sinatra app, so you can just call rackup like you would do with rails if you weren't using the rails command, so rails, anytime you see a config.ru, that's a rackup file, and that is rack's way of saying: This is a web application that we're going to have, so just run rackup to start that. With this one we have to set that secret key, and so we have to say SHARE_MEOW_SECRET_KEY=1 rackup and then we can run rackup. This will start our Puma server, and then start the ShareMeow service on port 9292. If we open our browser to localhost:9292, you're going to see that ShareMeow is running, but it doesn't really do anything. That's because we have to access those custom urls, and those urls to generate images require a bunch of parameters, so it's going to be easiest if we go back here, and we grab that ruby gem to make that work. You could do all of this stuff in regular old ruby, requiring those libraries and then generating a url and then replacing this with localhost:9292, but we're going to do that using the ShareMeow ruby gem, so let's go ahead and add this to our gemfile in our example share application, so we'll go ahead and do that, and then we'll configure the ShareMeow library in order to reference localhost:9292 and then we'll use the same secret key that we set here in the command line, which is going to just simply be the number one. In production, you're going to want to use a really long string here, in development you can go with whatever you want, so long as you share the same key on your rails application and ShareMeow's Sinatra application. Opening up the Gemfile in our rails application, I'm just going to paste in the ShareMeow client into the gemfile, and we're going to edit the config/initializers/sharemeow.rb file. Really in here, all you just need to do is paste in their config and change the url to localhost:9292, and your secret key to match. I would recommend you not hardcoding this, because you're going to change this in development and production possibly staging, and so on. So it would be probably a good idea to either run different instances of ShareMeow on development than your production one, or you could opt to run all the same one, but that's your call. If you would like to run different versions of ShareMeow for development and production, you can change this to

config.base_url = Rails.application.secrets.share_meow_url 
config.secret_key = '1'

Then you could open up

secrets.yml

development:
    secret_key_base: #Long_number_here 
    share_meow_url: 'http://localhost:9292'

I highly recommend you do the same thing for your secret key, and I'm just going to the same thing for your secret key and I'm just going to undo all of this for the example though, and I'm just going to hardcode those accordingly. You'll need to make sure that you leave your ShareMeow server running, and it's going to be in a separate tab in your terminal, so you'll other have this other rack up thing running, and that's just going to be necessary for you to generate those images. That's just how microservices work, you'll have multiple servers running for each of those microservices. Now that we have that, we need to start generating the parameters for the url here, and to go about learning how we can go do that, we can open up ShareMeow and take a look at what's available. If we look in the app/image_template folder, you're going to see there are three files. The base, which gives some default helpers to your templates, and then there's a comment in hello_world.rb templates. These are example templates, and we're going to use those in our application, but just know that you can go create your own and edit these accordingly, so you can tell it to use different erb templates, different CSS stylesheets and the list of allowed options can be defined as well. You'll see that this one, the hello world is really simple, and it's going to generate a or the subject, it will add emojis anywhere that are necessary, and that is going to generate a different image than the hello world one. These are going to be specified based upon this class name from inside your rails app. If we look back at the client, you'll see that they have an example here, and let's copy that into our rails app, just to see how we can render the hello world template. Hopping over to our rails application, we want to add a share link down here like I showed you before, and that's going to render out this comment in an image. All we really have to do is go down here and add a regular old link_to and we'll just call it

app/views/comments/_comments.html.erb

<%= link_to "Share", ShareMeowClient.image_url(template: 'HelloWorld', options: { message: 'Hi' }) %>

We're going to use the "Hello World" template, and we're going to pass in the options that are required for that, and that's the only thing that's required, is the message and actually, to do a better example here, we can change this to an image tag, and then we'll be able to embed it on the page and you'll be able to see it, and you'll notice that ti takes a second to render, and that's because you are rendering your rails application the image is being requested, it goes over to those microservice, the microservice takes the parameters, parses them, generates the image, returns the image and then finally you'll see it show up on the page, but that will take some time because it's doing quite a bit of work. The recommended thing here of course is to make sure that these images are behind a CDN so that every time that you generate one of these images, you only generate it one time, and it gets cached and everybody can view the cached version so your microservice isn't getting overburdened with work, because it's definitely going to be quite a bit of work, so here is the "Hello World" one, and it's working just as we would expect. Now we want the text to match, so if we change "Hi" in the message for comment.body, you can reference any oof the variables you would like in the rails app, and voila, we have sent over the text from this comment into the hello world template.

One cool thing too, is that while we might be sending over plain text right now, you could send over the simple formatted version of that, and that will generate simple formatted html and we'll send that over and it will actually embed the html version as well. This is really nifty, and we can send over custom html into the image generator and it will go ahead and do that for us. Not too much work to set that up, you just pretty much run a server, you share a key between them, configure rails app to help generate links, and you're basically done. That's really just generating the hello world template, so if we were to change this template to the comment template that we were looking at just now, you will notice that it has a lot more of allowed options, so if we were to go refresh this page, you're going to see that image is broken, and the reason for that is because when you go back to the log from the ShareMeow microservice, you're going to see that there's some errors. Anytime that your microservice doesn't get the right parameters, it's going to crash. The reason for this crash is because we didn't pass in the correct parameters and the code was expecting not a nil but a string, so that was the problem there. We need to make sure that we take all of these-- we'll pass that in as options, and then each one of these needs to be set to something, so we'll do comment.body

app/views/comments/_comments.html.erb

<%= link_to "Share", ShareMeowClient.image_url(template: 'HelloWorld', options: { content: comment.body, name: comment.user.email, user_id: comment.user.id, subject_name: "Comment", min_height: 400 }) %>

If we refresh this page, we'll see that we do get a image posted here. Now it's not completely correct because the avatar here is coming from producthunt's other service, and you can see how that works by going back into ShareMeow's code, and then looking at the views, comment.erb and this is actually where the template is rendered. This is very similar to rails templates, but you'll notice we're accessing the options variable, and that is because we're not using rails, you're not accessing instance variables like add options or @min_height or anything like that, you are just passing in the options hash and you have to access each of these values there. When I passed in the user id, this is actually pulling a user id from their other service, so you will need to go and edit all the html inside these app/views yourself in order to customize how that works. Here we have that full freedom to do whatever we like, so for this obviously, we don't want to be using the producthunt's avatars, so we could either get rid of this url and replace it with our own, so maybe we have, let's change it and say: options[:gravatar_url] and we can go do that. Let's change our comment class and change user id to gravatar url, and then we can go back to our code here and we can say gravatar_url: gravatar I've already installed the gem called gravatar_image_tag. If you're not familiar with that, it's super duper useful, gravatar is a service to take email addresses and get images avatar's back, and it has these awesome little helpers that will help generate those urls. If you take gravatar image url as the method, and you pass in an email address, you can then send over a gravatar image and then you could render this and if everything went correctly, and it looks like it has some url there, but it's not correctly being passed over. If we want to debug tihs and figure out how we can customize this and make sure that it's all working correctly you can actually just simply output your options values right into the image, and because this is generating it every single time you refresh the page and it's not caching it, you'll be able to see the output in the image. We might need to do an inspect here to see, and it is probably because that value is nil, and so you'll see nil come out, that means that our value isn't being passed in correctly or something, so we have to check out what we've got here, so we've got gravatar url there, and our erb template has gravatar_url ther, and it's specified correctly, so then we can go back to our code again, and make sure that that gravatar url is specified correctly there, and then we could also print out on the page

<%= gravatar.image_url(comment.user.email) %>

And make sure that that is being done correctly in the rails app, so we have this, and then we pass it into the gravatar thing, and so if we did like .jpeg here, we should get that, but as you might have noticed, the file type seems to have been missing from that url. We need to go back and add our file type here, let's say filetype: png and then go back, remove that code and then refresh, and now we should see the avatar there but we don't, so that means that something is still missing that we're sending across there. One thing that you probably need to do is to restart your ShareMeow server anytime you make changes to those templates like that, and that' because there's new values that are being available in the class, and while the templates may get reloaded every single request, the parameters, the options that you pass over are not necessarily, so anytime you make changes to those you'll have to restart that, but that might also be something that we could fix in the ShareMeow development service, to work a little bit more like rails where it autoreloads the classes every time. Now you're seeing that we have an avatar, this is my gravatar up here, but now it's being loaded inside the producthunt's ShareMeow comment CSS like image and everything. So this is super duper cool, we're able to also pull in images and emojis and anything that we want, so basically, we're able to generate images with any sort of format we could possibly imagine. We just have to make sure that all of our code is available in the ShareMeow's servers.

That is really all there is to it, we can go and edit some CSS, but this is pretty much as you would expect, you could change the colors, and maybe just say: Let's just make these links as blue or let's go back and say like: The color for the body should be blue, so if we were to refresh this, we should see the text has changed to the color blue. Now, if you're interested in building your own image templates, all you have to do is copy comment, that would probably be the best example to copy, and you can just create a new file with your own template there, and so you'll create your own CSS templates and view file and then you are off to the races. This will automatically get loaded in images templates when you create a new one, and you'll be able to pass in any of the options you would like over to that, and if you want to override or preparse some of those to the views, you can just set them simply by saying @options and the value or the key, and set the value to the parsed version of that. If you wanted to support markdown, you can use html pipeline or any of the markdown gems to automatically parse that and then send that out to your template in order to be rendered. That is that.

Really there's not a whole lot to it, you don't have all the niceties of rails, but you don't need the niceties of rails because this is really intended to be really lightweight, super fast and if you throw this up on your own service, you'll be able to use it for any of your applications that want to take advantage of this and if look at the rest of the code it's really straightforward, the ShareMeow image that we were looking at before really just initializes the templates and then uses this image kit class in order to load up the stylesheets and then render an image, so it takes some options and then renders it out, and that's it.

If you're interested in looking at that more, you can check out the image_kit gem, which we might talk about more in the future. This gem is pretty much super duper lightweight wrapper for image_kit, it just adds those urls in Sinatra into it in order to do the validations of the secret key and then do the rendering of your templates, so it's really organized and really easy to use. I think they did a super good job with this, the only thing I would like to see is I would love to see somebody make a way for you to test these out in the browser, and so maybe you have in development a way of seeing this before it gets rendered through an image a little bit faster or something. Not a huge need because it doesn't take very long to generate an image. That is ShareMeow and how to build image comments like you would see on Medium, and it's also a whirlwind introduction to microservices. They're all just a little applications like this gem, that don't do much but one specific thing, so I hope you enjoyed this episode and I will talk to you in the next one. Peace

Discussion


Login or create an account to join the conversation.