Blog

Tagged by 'cdn'

  • For the Gatsby version of my website, currently in development, I am serving all my images from Imagekit.io - a global image CDN. The reasons for doing this is so I will have the ultimate flexibility in how images are used within my site, which didn’t necessarily fit with what Gatsby has to offer especially when it came to how I wanted to position images within blog post content served from markdown files.

    As I understand it, Gatsby Image has two methods of responsively resizing images:

    1. Fixed: Images that have a fixed width and height.
    2. Fluid: Images that stretch across a fluid container.

    In my blog posts, I like to align my images (just take look at my post about my time in the Maldives) as it helps break the post up a bit. I won’t be able to achieve that look by the options provided in Gatsby. It’ll look all a little bit too stacked. The only option is to serve my images from Imagekit.io, which in the grand scheme isn’t a bad idea. I get the benefit of being able to transform images on the fly, optimisation (that can be customised through Imagekit.io dashboard) and fast delivery through its content-delivery network.

    To meet my image requirements, I decided to develop a custom responsive image component that will perform the following:

    • Lazyload image when visible in viewport.
    • Ability to parse an array “srcset" sizes.
    • Set a default image width.
    • Render the image on page load in low resolution.

    React Visibility Sensor

    The component requires the use of "react-visibility-sensor” plugin to mimic the lazy loading functionality. The plugin notifies you when a component enters and exits the viewport. In our case, we only want the sensor to run once an image enters the viewport. By default, the sensor is always fired every time a block enters and exits the viewport, causing our image to constantly alternate between the small and large versions - something we don't want.

    Thanks for a useful post by Mark Oskon, he provided a solution that extends upon the react-visibility-sensor plugin and allows us to turn off the sensor after the first reveal. I ported the code from Mark's solution in a newly created component housed in "/core/visibility-sensor.js", which I then reference into my LazyloadImage component:

    import React, { Component } from "react";
    import PropTypes from "prop-types";
    import VSensor from "react-visibility-sensor";
    
    class VisibilitySensor extends Component {
      state = {
        active: true
      };
    
      render() {
        const { active } = this.state;
        const { once, children, ...theRest } = this.props;
        return (
          <VSensor
            active={active}
            onChange={isVisible =>
              once &&
              isVisible &&
              this.setState({ active: false })
            }
            {...theRest}
          >
            {({ isVisible }) => children({ isVisible })}
          </VSensor>
        );
      }
    }
    
    VisibilitySensor.propTypes = {
      once: PropTypes.bool,
      children: PropTypes.func.isRequired
    };
    
    VisibilitySensor.defaultProps = {
      once: false
    };
    
    export default VisibilitySensor;
    

    LazyloadImage Component

    import PropTypes from "prop-types";
    import React, { Component } from "react";
    import VisibilitySensor from "../core/visibility-sensor"
    
    class LazyloadImage extends Component {
        render() {
          let srcSetAttributeValue = "";
          let sanitiseImageSrc = this.props.src.replace(" ", "%20");
    
          // Iterate through the array of values from the "srcsetSizes" array property.
          if (this.props.srcsetSizes !== undefined && this.props.srcsetSizes.length > 0) {
            for (let i = 0; i < this.props.srcsetSizes.length; i++) {
              srcSetAttributeValue += `${sanitiseImageSrc}?tr=w-${this.props.srcsetSizes[i].imageWidth} ${this.props.srcsetSizes[i].viewPortWidth}w`;
    
              if (this.props.srcsetSizes.length - 1 !== i) {
                srcSetAttributeValue += ", ";
              }
            }
          }
    
          return (
              <VisibilitySensor key={sanitiseImageSrc} delayedCall={true} partialVisibility={true} once>
                {({isVisible}) =>
                <>
                  {isVisible ? 
                    <img src={`${sanitiseImageSrc}?tr=w-${this.props.widthPx}`} 
                          alt={this.props.alt}
                          sizes={this.props.sizes}
                          srcSet={srcSetAttributeValue} /> : 
                    <img src={`${sanitiseImageSrc}?tr=w-${this.props.defaultWidthPx}`} 
                          alt={this.props.alt} />}
                  </>
                }
              </VisibilitySensor>
          )
        }
    }
    
    LazyloadImage.propTypes = {
      alt: PropTypes.string,
      defaultWidthPx: PropTypes.number,
      sizes: PropTypes.string,
      src: PropTypes.string,
      srcsetSizes: PropTypes.arrayOf(
        PropTypes.shape({
          imageWidth: PropTypes.number,
          viewPortWidth: PropTypes.number
        })
      ),
      widthPx: PropTypes.number
    }
    
    LazyloadImage.defaultProps = {
      alt: ``,
      defaultWidthPx: 50,
      sizes: `50vw`,
      src: ``,
      widthPx: 50
    }
    
    export default LazyloadImage
    

    Component In Use

    The example below shows the LazyloadImage component used to serve a logo that will serve a different sized image with the following widths - 400, 300 and 200.

    <LazyloadImage 
                    src="https://ik.imagekit.io/surinderbhomra/Pages/logo-me.jpg" 
                    widthPx={400} 
                    srcsetSizes={[{ imageWidth: 400, viewPortWidth: 992 }, { imageWidth: 300, viewPortWidth: 768 }, { imageWidth: 200, viewPortWidth: 500 }]}
                    alt="Surinder Logo" />
    

    Useful Links

    https://alligator.io/react/components-viewport-react-visibility-sensor/ https://imagekit.io/blog/lazy-loading-images-complete-guide/ https://www.sitepoint.com/how-to-build-responsive-images-with-srcset/

  • This month I've been writing some blog posts on why I decided to start using Cloudflare service for my website and utilising its API to allow me to purge cached files from the Cloudflare CDN on demand. Before reading further, I highly suggest perusing those posts just to put everything into context for my reasoning into using Cloudflare as well as the C# code that interacts with the API, which I will be referencing later on within this very post.

    My intial Cloudflare integration evolves around serving media files more efficiently through a CDN and having the ability to refresh these files automatically as updates are made within the Kentico CMS. Cloudflare's CDN services can help cache your content across their large global network, moving static files closer to your visitor.

    Based on the Page Rules I configured within the Cloudflare dashboard, I am caching all media library files served through the /getmedia/ URL path into the Cloudflare CDN. The same file will be served through the CDN until the set cache limit has expired. We need to implement functionality that will add some automation to the Kentico platform to purge the cache of a specific media library file when updated.

    Add A Global Event

    I created an event handler for the updating of Media library files as I wanted to get details of the file being updated by leveraging the MediaFileInfo class to access the Update.After event.

    protected override void OnInit()
    {
        base.OnInit();
    
        MediaFileInfo.TYPEINFO.Events.Update.After += Update_After;
    }
    
    private void Update_After(object sender, ObjectEventArgs e)
    {
        MediaFileInfo fileInfo = e.Object as MediaFileInfo;
    
        GlobalEventFunctions.PurgeMediaCache(fileInfo);
    }
    

    PurgeMediaCache() Method

    The event above calls a GlobalEventFunctions.PurgeMediaCache() method that will pass the information about the changed file ready for purging. The file URL parsed to the Cloudflare.PurgeSelectedFiles() method needs to be exact and take into consideration how your instance of Kentico is serving media files. If Permanent URL's are being used the /getmedia/ URL needs to be constructed consisting of:

    • Current domain
    • File GUID
    • File Name
    • File Extension

    Otherwise, we can just use get the file path as normal to where the media file resides.

    public class GlobalEventFunctions
    {
        /// <summary>
        /// Purges a file from the Cloudflare cache.
        /// </summary>
        /// <param name="fileInfo"></param>
        public static void PurgeMediaCache(MediaFileInfo fileInfo)
        {
            bool permanentURLEnabled = SettingsKeyInfoProvider.GetBoolValue($"{SiteContext.CurrentSiteName}.CMSMediaUsePermanentURLs");
            string filePath = string.Empty;
                
            if (permanentURLEnabled)
                filePath = $"{GetCurrentDomain()}/getmedia/{fileInfo.FileGUID.ToString()}/{fileInfo.FileName}{fileInfo.FileExtension}";
            else
                filePath = $"{GetCurrentDomain()}/{fileInfo.FilePath}";
    
            try
            {
                // Get code from: https://www.surinderbhomra.com/Blog/Post/2018/11/11/Cloudflare-API-Purge-Files-By-URL-In-C
                CloudflareCacheHelper cloudflareHelper = new CloudflareCacheHelper();
    
                cloudflareHelper.PurgeSelectedFiles(new List<string> { filePath });
            }
            catch (Exception ex)
            {
                EventLogProvider.LogException("Cloudflare Purge File Cache", "CLOUDFLARE_PURGE", ex, SiteContext.CurrentSiteID, $"Purge File: {filePath}");
            }
        }
    
        /// <summary>
        /// Get domain from current http context.
        /// </summary>
        /// <returns></returns>
        private static string GetCurrentDomain()
        {
            return $"{HttpContext.Current.Request.Url.Scheme}{Uri.SchemeDelimiter}{HttpContext.Current.Request.Url.Host}{(!HttpContext.Current.Request.Url.IsDefaultPort ? $":{HttpContext.Current.Request.Url.Port}" : null)}";
        }
    }
    

    We need not consider any other scenarios, such as insert or deletion. If a file is inserted, there is nothing to purge as it's a new file that will be cached directly into in the CDN on first request and when it comes to deletion we can just wait for the cache to expire.

    What's Next?

    The integration I have detailed so far is just scratching the surface of what Cloudflare has to offer and will investigate further on pushing more content over to the CDN. One area, in particular, I am looking into is carrying out full page caching. You might be thinking why even bother as Kentico has pretty good caching mechanisms already in place?

    Well Cloudflare has a really neat feature called "Always Online", where a cached version of a page is served if on the off chance it happens to go down or requires a reboot to install key security updates. But implementing this feature requires strict Page Rules to be setup within the Cloudflare dashboard to ensure the general workings of Kentico are not effected.

  • A couple day ago my website got absolutely hammered by a wave of constant SQL injection attacks by the same IP over a time period of a couple hours.

    I only managed to notice this whilst perusing the Event Log within the Kentico Administration interface. I don't normally check my own error logs as regularly as I should do, but since my site has recently gone through a bit of a revamp (which I'm still yet to post about), I wanted to ensure I haven't broken anything.

    To be honest, I am flattered that someone would think this site is worth the time and energy in trying to hack my site. Trust me, it ain't worth it.

    Even though Kentico has handled these attacks well, as a precaution I wanted to implement an additional layer of security before any further untoward activity reaches to my site. Being on a shared hosting platform my options are limited and my hands are tied to put in an infrastructure that doesn't cost the world.

    Enter Cloudflare

    I have always had some form of awareness of the Cloudflare content delivery network, just never put the service into practice. At one point I was looking into utilising Cloudflare to manage all my site media files over their CDN. But Cloudflare isn't just a CDN, it's able to offer much more:

    • Analytics - monitor traffic as well as caching ratio and more!
    • Firewall- manage access by IP, country, or query rules.
    • Rate limiting- protect your site or API from malicious traffic by blocking client IP addresses that hit a URL pattern and exceed a threshold you define.
    • Page rules - to allow caching to be triggered by a number of rules targeting specific areas of a site.

    The great thing is that these options are part of the free plan... even though there are restrictions to the number of settings you are able to put in place. The security options alone was enough of a reason to try out Cloudflare. But for my needs, the free plan seemed to tick all the boxes. I am just scratching the surface to what Cloudflare has to offer and have already got a few of the features working alongside Kentico.

    How I'm Using Cloudflare With Kentico

    As security was a main concern for me, one of the first things I did was to add in some rules through the firewall and block suspicious traffic. Naturally the next was to take advantage of the CDN capabilities. I wouldn't recommend full site caching unless you have some pretty strict page rules in place as this has a chance to cause issues with the Kentico Admin interface. By default, Cloudflare doesn't cache HTML pages, which is a good thing as it gives us a plain canvas to target the areas we want to cache.

    Being on the free plan, I only had three page rules at my disposal and made the decision to cache the following parts of my site.

    Area Regex Rule Settings
    Media Library Files (using Permanent URL's) *surinderbhomra.com/
    	getmedia/\* | <br>**Cache Level:** Caches Everything<br>**Edge Cache TTL:** A month<br>**Browser Cache TTL:** 16 days<br> |
    

    | Site CSS, JavaScript and Images | *surinderbhomra.com/
    resources/* | Cache Level: Caches Everything
    Edge Cache TTL: A month
    Browser Cache TTL: 16 days | | ScriptResource.axd/WebResource.axd | *surinderbhomra.com/*.axd | Cache Level: Caches Everything
    Edge Cache TTL: 7 days
    Browser Cache TTL: 7 days |

    Two cache types are used:

    1. Edge Cache TTL - is the setting that controls how long CloudFlare's edge servers will cache a resource before requesting a fresh copy from your server.
    2. Browser Cache TTL - is the time that CloudFlare instructs a visitor's browser to cache a resource. Until this time expires, the browser will load the resource from its local cache and speeding up the request.

    I am caching my assets for a considerable amount of time, which begs the question how will changes to files purge the cache in Cloudflare? Luckily, Cloudflare has quite a nice API, where I have the ability to purge everything or individual files (maximum of 30 in one request).

    Purge Media Library File Cache

    I am in the middle of testing a GlobalEvent handler that will carry out the following steps when files are inserted or updated:

    • Get path of the file.
    • Check if site is using Permanent URL's. As the URL to the file will be constructured differently if enabled.
    • Convert the relative path to an absolute path.
    • Pass the absolute file path to Cloudflare using the following Purge Files by URL API endpoint.

    Once I have carried out some further tests, I will be posting this code in a follow-up post.

    Purge Site File Cache

    Now attempting to purge the cache for site files such as JavaScript, CSS and images are a little more tricky as I need to keep an eye on the files changed. The easiest thing to do is I could write some code that will iterate through all the files in the /resources folder and purge everything from the CDN. Not the most elegant solution. Still, need to ponder on the correct implementation. If anyone has got any better suggestions, let me know.

    How Do I Know Cloudflare Is Caching My Site Files?

    It does take a little time in Cloudflare to cache all files on a site. It all depends on pages being viewed. A page needs to be loaded in order to submit all its contents to cache. On first load, the response header CF-Cache-Status will return a "MISS", which means the content has not been served from Cloudflare.

    Cloudflare Response - MISS

    However, when you go back to the page and re-check the page headers, the CF-Cache-Status should return "HIT". If this is not the case, check your Page Rules within the Cloudflare dashboard.

    Cloudflare Response - HIT

    Is Cloudflare Worth It?

    Quick answer - Yes!

    Setup is very straight-forward. All that is required is to carry out a change to your domain and point your DNS to the DNS Cloudflare assigns to you. There is no downtime in doing this. As a result, overall site performance has improved and page speed test faired much better.

    To give you a better insight for transparency, here are some statistics straight from my Cloudflare portal over a 24 hour period:

    Requests Through Cloudflare
    (Understandably, the number of cached items served through Cloudflare is low due to only caching specific areas)

    Cloudflare Performance
    (My basic shared hosting should now be performing better as less requests are being served via the origin server)

    Cloudflare Detected Threats
    (Blocked threats - the reason why I decided to give Cloudflare a try in the first place)

    I still have to carry out a lot more research in using Cloudflare to its full potential and will use my website as a test bed to see what I can achieve. My end goal is to make my website quicker and more secure!