github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/blog/content/from-zero-to-go-launching-on-google.article (about)

     1  From zero to Go: launching on the Google homepage in 24 hours
     2  13 Dec 2011
     3  Tags: appengine, google, guest
     4  
     5  Reinaldo Aguiar
     6  
     7  * Introduction
     8  
     9  _This_article_was_written_by_Reinaldo_Aguiar,_a_software_engineer_from_the_Search_team_at_Google._He_shares_his_experience_developing_his_first_Go_program_and_launching_it_to_an_audience_of_millions_-_all_in_one_day!_
    10  
    11  I was recently given the opportunity to collaborate on a small but highly visible "20% project": the [[http://www.google.com/logos/2011/thanksgiving.html][Thanksgiving 2011 Google Doodle]]. The doodle features a turkey produced by randomly combining different styles of head, wings, feathers and legs. The user can customize it by clicking on the different parts of the turkey. This interactivity is implemented in the browser by a combination of JavaScript, CSS and of course HTML, creating turkeys on the fly.
    12  
    13  .image from-zero-to-go-launching-on-google_image00.png
    14  
    15  Once the user has created a personalized turkey it can be shared with friends and family by posting to Google+. Clicking a "Share" button (not pictured here) creates in the user's Google+ stream a post containing a snapshot of the turkey. The snapshot is a single image that matches the turkey the user created.
    16  
    17  With 13 alternatives for each of 8 parts of the turkey (heads, pairs of legs, distinct feathers, etc.) there are more than than 800 million possible snapshot images that could be generated. To pre-compute them all is clearly infeasible. Instead, we must generate the snapshots on the fly. Combining that problem with a need for immediate scalability and high availability, the choice of platform is obvious: Google App Engine!
    18  
    19  The next thing we needed to decide was which App Engine runtime to use. Image manipulation tasks are CPU-bound, so performance is the deciding factor in this case.
    20  
    21  To make an informed decision we ran a test. We quickly prepared a couple of equivalent demo apps for the new [[http://code.google.com/appengine/docs/python/python27/newin27.html][Python 2.7 runtime]] (which provides [[http://www.pythonware.com/products/pil/][PIL]], a C-based imaging library) and the Go runtime. Each app generates an image composed of several small images, encodes the image as a JPEG, and sends the JPEG data as the HTTP response. The Python 2.7 app served requests with a median latency of 65 milliseconds, while the Go app ran with a median latency of just 32 milliseconds.
    22  
    23  This problem therefore seemed the perfect opportunity to try the experimental Go runtime.
    24  
    25  I had no previous experience with Go and the timeline was tight: two days to be production ready. This was intimidating, but I saw it as an opportunity to test Go from a different, often overlooked angle: development velocity. How fast can a person with no Go experience pick it up and build something that performs and scales?
    26  
    27  * Design
    28  
    29  The approach was to encode the state of the turkey in the URL, drawing and encoding the snapshot on the fly.
    30  
    31  The base for every doodle is the background:
    32  
    33  .image from-zero-to-go-launching-on-google_image01.jpg
    34  
    35  A valid request URL might look like this: [[http://google-turkey.appspot.com/thumb/20332620][http://google-turkey.appspot.com/thumb/20332620]]
    36  
    37  The alphanumeric string that follows "/thumb/" indicates (in hexadecimal) which choice to draw for each layout element, as illustrated by this image:
    38  
    39  .image from-zero-to-go-launching-on-google_image03.png
    40  
    41  The program's request handler parses the URL to determine which element is selected for each component, draws the appropriate images on top of the background image, and serves the result as a JPEG.
    42  
    43  If an error occurs, a default image is served. There's no point serving an error page because the user will never see it - the browser is almost certainly loading this URL into an image tag.
    44  
    45  * Implementation
    46  
    47  In the package scope we declare some data structures to describe the elements of the turkey, the location of the corresponding images, and where they should be drawn on the background image.
    48  
    49  	var (
    50  	    // dirs maps each layout element to its location on disk.
    51  	    dirs = map[string]string{
    52  	        "h": "img/heads",
    53  	        "b": "img/eyes_beak",
    54  	        "i": "img/index_feathers",
    55  	        "m": "img/middle_feathers",
    56  	        "r": "img/ring_feathers",
    57  	        "p": "img/pinky_feathers",
    58  	        "f": "img/feet",
    59  	        "w": "img/wing",
    60  	    }
    61  
    62  	    // urlMap maps each URL character position to
    63  	    // its corresponding layout element.
    64  	    urlMap = [...]string{"b", "h", "i", "m", "r", "p", "f", "w"}
    65  
    66  	    // layoutMap maps each layout element to its position
    67  	    // on the background image.
    68  	    layoutMap = map[string]image.Rectangle{
    69  	        "h": {image.Pt(109, 50), image.Pt(166, 152)},
    70  	        "i": {image.Pt(136, 21), image.Pt(180, 131)},
    71  	        "m": {image.Pt(159, 7), image.Pt(201, 126)},
    72  	        "r": {image.Pt(188, 20), image.Pt(230, 125)},
    73  	        "p": {image.Pt(216, 48), image.Pt(258, 134)},
    74  	        "f": {image.Pt(155, 176), image.Pt(243, 213)},
    75  	        "w": {image.Pt(169, 118), image.Pt(250, 197)},
    76  	        "b": {image.Pt(105, 104), image.Pt(145, 148)},
    77  	    }
    78  	)
    79  
    80  The geometry of the points above was calculated by measuring the actual location and size of each layout element within the image.
    81  
    82  Loading the images from disk on each request would be wasteful repetition, so we load all 106 images (13 * 8 elements + 1 background + 1 default) into global variables upon receipt of the first request.
    83  
    84  	var (
    85  	    // elements maps each layout element to its images.
    86  	    elements = make(map[string][]*image.RGBA)
    87  
    88  	    // backgroundImage contains the background image data.
    89  	    backgroundImage *image.RGBA
    90  
    91  	    // defaultImage is the image that is served if an error occurs. 
    92  	    defaultImage *image.RGBA
    93  
    94  	    // loadOnce is used to call the load function only on the first request.
    95  	    loadOnce [[http://golang.org/pkg/sync/#Once][sync.Once]]
    96  	)
    97  
    98  	// load reads the various PNG images from disk and stores them in their
    99  	// corresponding global variables.
   100  	func load() {
   101  	    defaultImage = loadPNG(defaultImageFile)
   102  	    backgroundImage = loadPNG(backgroundImageFile)
   103  	    for dirKey, dir := range dirs {
   104  	        paths, err := filepath.Glob(dir + "/*.png")
   105  	        if err != nil {
   106  	            panic(err)
   107  	        }
   108  	        for _, p := range paths {
   109  	            elements[dirKey] = append(elements[dirKey], loadPNG(p))
   110  	        }
   111  	    }
   112  	}
   113  
   114  Requests are handled in a straightforward sequence:
   115  
   116  - Parse the request URL, decoding the decimal value of each character in the path.
   117  
   118  - Make a copy of the background image as the base for the final image.
   119  
   120  - Draw each image element onto the background image using the layoutMap to determine where they should be drawn.
   121  
   122  - Encode the image as a JPEG
   123  
   124  - Return the image to user by writing the JPEG directly to the HTTP response writer.
   125  
   126  Should any error occur, we serve the defaultImage to the user and log the error to the App Engine dashboard for later analysis.
   127  
   128  Here's the code for the request handler with explanatory comments:
   129  
   130  	func handler(w http.ResponseWriter, r *http.Request) {
   131  	    // [[http://blog.golang.org/2010/08/defer-panic-and-recover.html][Defer]] a function to recover from any panics.
   132  	    // When recovering from a panic, log the error condition to
   133  	    // the App Engine dashboard and send the default image to the user.
   134  	    defer func() {
   135  	        if err := recover(); err != nil {
   136  	            c := appengine.NewContext(r)
   137  	            c.Errorf("%s", err)
   138  	            c.Errorf("%s", "Traceback: %s", r.RawURL)
   139  	            if defaultImage != nil {
   140  	                w.Header().Set("Content-type", "image/jpeg")
   141  	                jpeg.Encode(w, defaultImage, &imageQuality)
   142  	            }
   143  	        }
   144  	    }()
   145  
   146  	    // Load images from disk on the first request.
   147  	    loadOnce.Do(load)
   148  
   149  	    // Make a copy of the background to draw into.
   150  	    bgRect := backgroundImage.Bounds()
   151  	    m := image.NewRGBA(bgRect.Dx(), bgRect.Dy())
   152  	    draw.Draw(m, m.Bounds(), backgroundImage, image.ZP, draw.Over)
   153  
   154  	    // Process each character of the request string.
   155  	    code := strings.ToLower(r.URL.Path[len(prefix):])
   156  	    for i, p := range code {
   157  	        // Decode hex character p in place.
   158  	        if p < 'a' {
   159  	            // it's a digit
   160  	            p = p - '0'
   161  	        } else {
   162  	            // it's a letter
   163  	            p = p - 'a' + 10
   164  	        }
   165  
   166  	        t := urlMap[i]    // element type by index
   167  	        em := elements[t] // element images by type
   168  	        if p >= len(em) {
   169  	            panic(fmt.Sprintf("element index out of range %s: "+
   170  	                "%d >= %d", t, p, len(em)))
   171  	        }
   172  
   173  	        // Draw the element to m,
   174  	        // using the layoutMap to specify its position.
   175  	        draw.Draw(m, layoutMap[t], em[p], image.ZP, draw.Over)
   176  	    }
   177  
   178  	    // Encode JPEG image and write it as the response.
   179  	    w.Header().Set("Content-type", "image/jpeg")
   180  	    w.Header().Set("Cache-control", "public, max-age=259200")
   181  	    jpeg.Encode(w, m, &imageQuality)
   182  	}
   183  
   184  For brevity, I've omitted several helper functions from these code listings. See the [[http://code.google.com/p/go-thanksgiving/source/browse/][source code]] for the full scoop.
   185  
   186  * Performance
   187  
   188  [[http://3.bp.blogspot.com/-3dpdQWv1nzQ/TufkFj0bS-I/AAAAAAAAAEg/hONAssh_D9c/s1600/image02.png][.image from-zero-to-go-launching-on-google_image02.png ]]
   189  
   190  This chart - taken directly from the App Engine dashboard - shows average request latency during launch. As you can see, even under load it never exceeds 60 ms, with a median latency of 32 milliseconds. This is wicked fast, considering that our request handler is doing image manipulation and encoding on the fly.
   191  
   192  * Conclusions
   193  
   194  I found Go's syntax to be intuitive, simple and clean. I have worked a lot with interpreted languages in the past, and although Go is instead a statically typed and compiled language, writing this app felt more like working with a dynamic, interpreted language.
   195  
   196  The development server provided with the [[http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Go][SDK]] quickly recompiles the program after any change, so I could iterate as fast as I would with an interpreted language. It's dead simple, too - it took less than a minute to set up my development environment.
   197  
   198  Go's great documentation also helped me put this together fast. The docs are generated from the source code, so each function's documentation links directly to the associated source code. This not only allows the developer to understand very quickly what a particular function does but also encourages the developer to dig into the package implementation, making it easier to learn good style and conventions.
   199  
   200  In writing this application I used just three resources: App Engine's [[http://code.google.com/appengine/docs/go/gettingstarted/helloworld.html][Hello World Go example]], [[http://golang.org/pkg/][the Go packages documentation]], and [[http://blog.golang.org/2011/09/go-imagedraw-package.html][a blog post showcasing the Draw package]]. Thanks to the rapid iteration made possible by the development server and the language itself, I was able to pick up the language and build a super fast, production ready, doodle generator in less than 24 hours.
   201  
   202  Download the full app source code (including images) at [[http://code.google.com/p/go-thanksgiving/source/browse/][the Google Code project]].
   203  
   204  Special thanks go to Guillermo Real and Ryan Germick who designed the doodle.