Presentation is loading. Please wait.

Presentation is loading. Please wait.

today's class morning: how we got here HTTP overview High Performance Web Sites: Rules 1-6 break exercise: Web 100 stats High Performance Web Sites: Rules.

Similar presentations


Presentation on theme: "today's class morning: how we got here HTTP overview High Performance Web Sites: Rules 1-6 break exercise: Web 100 stats High Performance Web Sites: Rules."— Presentation transcript:

1

2 today's class morning: how we got here HTTP overview High Performance Web Sites: Rules 1-6 break exercise: Web 100 stats High Performance Web Sites: Rules 7-14 afternoon: morning wrap-up Even Faster Web Sites: chapters 1-4 break Even Faster Web Sites: chapters 5-8 exercise: web site performance analysis state of performance

3 logistics slides: install: Firebug - YSlow - Hammerhead – ask questions! candidate questions appear after each section – if you can't answer these, ask

4 how we got here HTTP overview HPWS Rules 1-6 break web 100 HPWS 7-14

5 17% 83% iGoogle, primed cache the importance of frontend performance 9%91% iGoogle, empty cache

6 time spent on the frontend Empty CachePrimed Cache search.live.com/results67%0% en.wikipedia.org/wiki94%91%

7 14 R ULES 1.M AKE FEWER HTTP REQUESTS 2.U SE A CDN 3.A DD AN E XPIRES HEADER 4.G ZIP COMPONENTS 5.P UT STYLESHEETS AT THE TOP 6.P UT SCRIPTS AT THE BOTTOM 7.A VOID CSS EXPRESSIONS 8.M AKE JS AND CSS EXTERNAL 9.R EDUCE DNS LOOKUPS 10.M INIFY JS 11.A VOID REDIRECTS 12.R EMOVE DUPLICATE SCRIPTS 13.C ONFIGURE ET AGS 14.M AKE AJAX CACHEABLE

8

9 evangelism Conferences Web 2.0 Expo The Ajax Experience OSCON Google/IO SXSW Companies Yahoo! Amazon Zillow Microsoft Conferences WordCamp Future of Web Apps Widget Summit Velocity Rich Web Experience Apple Netflix Twitter LinkedIn Google Facebook CBS Interactive

10

11

12 September 2007

13

14 June 2009

15 how we got here HTTP overview HPWS Rules 1-6 break web 100 HPWS 7-14

16 basic HTTP GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 Request HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 6230 function d(s) {... Response status code response headers request headers response body

17 compression Might want to set Vary:Accept-Encoding and Cache-Control:private GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 6230 function d(s) {... GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 Accept-Encoding: gzip,deflate HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 2066 Content-Encoding: gzip XmoÛHþ\ÿFÖvã*wØoq...

18 HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 2066 Content-Encoding: gzip XmoÛHþ\ÿFÖvã*wØoq... Expires Expiration date determines freshness. Can also use Cache-Control: max-age GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 Accept-Encoding: gzip,deflate HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 2066 Content-Encoding: gzip Expires: Fri, 26 Sep :00:00 GMT XmoÛHþ\ÿFÖvã*wØoq...

19 HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 2066 Content-Encoding: gzip Expires: Fri, 26 Sep :00:00 GMT XmoÛHþ\ÿFÖvã*wØoq... HTTP/ Not Modified Conditional GET (IMS) IMS determines validity. IMS is used when Reload is pressed. ETag and If-None-Match also determine validity. GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 Accept-Encoding: gzip,deflate GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 Accept-Encoding: gzip,deflate If-Modified-Since: Mon, 22 Sep :14:35 GMT sometime after 3pm PT 9/24/08:

20 questions What are the two key questions when reading resources from the cache? What is the request header and response header used to negotiate compression? What are the two response headers you can use to set an expiration date? What response header provides the file timestamp of the requested resource? What are the two request headers used to determine resource validity?

21 primed (same session) : 1 HTTP request, 15 cache reads 8Kb xferred seconds empty vs. primed cache empty: 30 HTTP requests 194Kb xferred seconds cache reads primed (diff session) : 4 HTTP requests, 28 cache reads 13Kb xferred seconds

22 memory cache Why is "primed cache same session" different from "primed cache different session"? Browsers store resources in memory so they don't need to read them from disk. What determines whether a resource is held in memory cache? I don't know. That'd be a good research project.

23 disk cache Two considerations with disk cache Is the resource fresh (vs. expired)? If it's expired, is it valid (vs. updated)? If a resource is fresh, no HTTP request is made – it's just read from disk. If a resource is expired, a Conditional GET request is made. If the resource is valid, it's read from disk and the Conditional GET response is empty. If the resource has been updated, the Conditional GET response contains the updated version.

24

25 packet sniffers measure HTTP requests HTTPWatch IE and Firefox, Windows only Firebug net panel less accurate timings (includes blocking time) others: AOL Pagetest (web-based), Fiddler (Windows), Wireshark (low-level), IBM Page Detailer (Windows)

26

27 Firebug Joe Hewitt, January 2006 Firebug Working Group, Mozilla came onboard kit and caboodle: inspect HTML CSS explanation and modification DOM inspector network monitor JavaScript console, log, debugger and profiler add-on to Firefox Firebug Lite – bookmarklet for IE, Safari, Opera, etc. Open Source (free)

28 YSlow

29 Steve Souders, July 2007 web performance analysis tool add-on to Firebug (extension to an extension) Open Source (free), not open repository

30 questions What's the white space in the HTTP profiles? Why is the HTML document typically not cached? Why are packet sniffers not good for measuring page load time?

31 how we got here HTTP overview HPWS Rules 1-6 break web 100 HPWS 7-14

32 17% 83% iGoogle, primed cache the importance of frontend performance 9%91% iGoogle, empty cache

33 definitions Backend Time from when the user makes the request to when the last byte of the HTML document arrives. Includes the time for the initial request to go up, the web server to stitch together the HTML, and for the response to come back. Frontend Shorthand for everything after the HTML document arrives. In reality, includes backend time (primarily reading static files) and network time, as well as true frontend activities such as parsing HTML, CSS, and JS, and executing JS.

34 time spent on the frontend Empty CachePrimed Cache search.live.com/results67%0% en.wikipedia.org/wiki94%91%

35 The Performance Golden Rule 80-90% of the end-user response time is spent on the frontend. Start there. greater potential for improvement simpler proven to work

36 Rule 1: Make Fewer HTTP Requests 80-90% of load time is the frontend the frontend time is dominated by HTTP HTTP requests growth since 2003: 25 to 50 * each HTTP request has overhead – even with persistent connections reducing HTTP requests has the biggest impact bigger benefit for users with higher latency parallelization reduces the need for this *

37 Rule 1: Make Fewer HTTP Requests But... is it possible to reduce HTTP requests without reducing richness? Yes: combine JS, CSS image maps CSS sprites inline images

38 combine JS and CSS not combining scripts with stylesheets multiple scripts => one script multiple stylesheets => one stylesheet apache module: YUI Combo Handler

39 image maps old school, CSS sprites is preferred image maps still useful when x,y coordinates are useful, for example, in maps

40 CSS sprites multiple CSS background images => one image

overall size reduced generator:

41 inline images (data: URLs) embed the content of an HTTP response in place of a URL Red Star if embedded in HTML document, probably not cached => embed in stylesheet instead base64 encoding increases total size works in IE8 (not IE7 and earlier)

42 data: URLs not just for images Hammerhead:

43 Rule 2: Use a CDN Content Delivery Network geographically distributed servers => closer to your users also: backups, storage, caching, absorb spikes Akamai, Mirror Image, Limelight, Savvis new: Amazon S3, Panther Express (more affordable) dynamic content: 1 HTTP request static content: all the rest distribute your static content before distributing your dynamic content

44 CDN – reverse proxy Edge Servers Origin Server(s) Developer User name server ISP's DNS Resolver CDN

45 CDN usage CDN search.live.com/resultsAkamai en.wikipedia.org/wiki

46 questions What's the most important requirement for a CDN? How can you find out which CDN a company uses? What plays the key role in sending users to the appropriate edge server?

47 Rule 3: Add an Expires Header Expiration date determines freshness. Can also use Cache-Control: max-age GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 Accept-Encoding: gzip,deflate HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 2066 Content-Encoding: gzip Expires: Mon, 12 Oct :57:34 GMT Cache-Control: max-age= XmoÛHþ\ÿFÖvã*wØoq...

48 Expires vs. max-age Expires works in HTTP/1.0, max-age in HTTP/1.1 Expires is an absolute date: 12 Oct :57:34 GMT max-age is # of seconds until expiration: Expires relies on clock synchronization between client and server for short expirations max-age takes precedence over Expires

49 sending Expires (Apache) mod_expires ExpiresDefault "access plus 1 year" sends both Expires and max-age: Expires: Mon, 12 Oct :57:34 GMT Cache-Control: max-age=

50 Expires in the wild – 2007 ImagesScriptsStylesheets % with Expires Median Age amazon.com 0/620/30/10%114 days aol.com 23/436/181/148%217 days cnn.com 0/1382/110/21%227 days ebay.com 16/200/70/255%140 days froogle.google.com 1/230/1 4%454 days msn.com 32/353/91/180%34 days myspace.com 0/180/2 0%1 day wikipedia.org 6/82/31/175%1 day yahoo.com 23/234/41/1100%na youtube.com 0/320/70/30%26 days average 10/40 (25%) 2/5 (38%) 0.5/2 (27%) 12/46 (26%) March 2007

51 Expires in the wild – 2008 ImagesScriptsStylesheets % with Expires Median Age aol.com 26/3513/201/171%189 days ebay.com 48/486/72/298%1 day facebook.com 93/9720/2220/2096%121 days google.com/search 1/10/10/050%1 day search.live.com/results 6/61/14/4100%na msn.com 45/453/3 100%na myspace.com 21/217/74/4100%na en.wikipedia.org/wiki 7/325/59/946%310 days yahoo.com 23/234/41/1100%na youtube.com 8/271/1 34%unk average 28/34 (83%) 6/7 (85%) 5/5 (100%) 38/45 (85%) October 2008

52 revving filenames (really, this is independent of Expires headers) once you make a resource public, you can never change it => aggressive proxies prevent 100% of users from getting the update best solution: change the filename date: trough_ gif version #: onload_1.6.1.js checksum: dom.common.js don't use querystring: wikibits.js?179 won't be cached by some proxies

53 questions What's are some differences between Expires and max-age? What types of resources should an Expires or a Cache-Control header be used with? Once a resource is cached with a far future expiration date, how can you push updates and ensure users get the new version?

54 Rule 4: Gzip Components typically reduces size by 70% ( )/6230 = 67% GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 6230 function d(s) {... GET /v-app/scripts/ dom.common.js HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…) Gecko/ Firefox/3.0.1 Accept-Encoding: gzip,deflate HTTP/ OK Content-Type: application/x-javascript Last-Modified: Mon, 22 Sep :14:35 GMT Content-Length: 2066 Content-Encoding: gzip XmoÛHþ\ÿFÖvã*wØoq...

55 gzip vs. deflate gzip (default settings) compresses more GzipDeflate Size SavingsSizeSavings Script3.3K1.1K67%1.1K66% Script39.7K14.5K64%16.6K58% Stylesheet1.0K0.4K56%0.5K52% Stylesheet14.1K3.7K73%4.7K67%

56 pros and cons Pro: smaller transfer size Con: CPU cycles – on client and server Don't compress resources < 1K

57 gzip configuration Apache 1.3: mod_gzip mod_gzip_item_include file \.html$ mod_gzip_item_include mime ^text/html$ mod_gzip_item_include file \.js$ mod_gzip_item_include mime ^application/x- javascript$ mod_gzip_item_include file \.css$ mod_gzip_item_include mime ^text/css$ Apache 2.x: mod_deflate AddOutputFilterByType DEFLATE text/html text/css application/x-javascript control compression level: DeflateCompressionLevel

58 HTMLScriptsStylesheets amazon.com x aol.com xsome cnn.com ebay.com x froogle.google.com xxx msn.com xdeflate myspace.com xxx wikipedia.org xxx yahoo.com xxx youtube.com xsome gzip: not just for HTML HTMLScriptsStylesheets aol.com xxx ebay.com xsome facebook.com xxx google.com/search xxna search.live.com/results xxx msn.com xxx myspace.com xxx en.wikipedia.org/wiki xsome yahoo.com xxx youtube.com xxx gzip scripts, stylesheets, XML, JSON (not images, Flash, PDF) March 2007 October 2008

59 edge case: proxies Proxy Origin Server 6 GET main.js (no Accept-Encoding) 2 GET main.js Accept-Encoding: gzip 3 main.js Content-Encoding: gzip 4 main.js Content-Encoding: gzip 5 main.js Content-Encoding: gzip 1 GET main.js Accept-Encoding: gzip 7 main.js Content-Encoding: gzip proxies may serve gzipped content to browsers that don't support it, and vice versa

60 edge case: proxies w/ Vary Proxy Origin Server 6 GET main.js (no Accept-Encoding) 2 GET main.js Accept-Encoding: gzip 3 main.js Content-Encoding: gzip Vary: Accept-Encoding 4 main.js Content-Encoding: gzip [Accept-Encoding: gzip] 5 main.js Content-Encoding: gzip 1 GET main.js Accept-Encoding: gzip 10 main.js (no gzip) 7 GET main.js (no Accept-Encoding) 9 main.js [Accept-Encoding: ] 8 main.js Vary: Accept-Encoding 11 GET main.js Accept-Encoding: gzip 12 main.js Content-Encoding: gzip 13 GET main.js (no Accept-Encoding) 14 main.js (no gzip) add Vary: Accept-Encoding

61 edge case: bad browsers < 1% of browsers have problems with gzip IE 5.5: IE 6.0: Netscape 3.x, 4.x User-Agent white list for gzip Apache 1.3: mod_gzip_item_include reqheader "User-Agent: MSIE [6-9]" mod_gzip_item_include reqheader "User-Agent: Mozilla/[5-9]" Apache 2.0: BrowserMatch ^MSIE [6-9] gzip BrowserMatch ^Mozilla/[5-9] gzip

62 edge case: bad browsers (cont'd) proxies could mix-up responses give cached response from useragent1 to useragent2 could add Vary: User-Agent so many possibilities, defeats proxy caching better to add Cache-Control: Private downside: disables all proxy caches is it a serious problem? hard to diagnose; problem getting smaller

63 edge case: ETags what happens when proxy makes Conditional GET requests? Last-Modified date for gzipped vs. ungzipped is different => If-Modified-Since works fine ETag is the same in Apache for gzipped & ungzipped => If-None-Match succeeds, proxy could give browser mismatched content remove Etags! (Rule 13)

64 edge case: ETags present Proxy Origin Server 6 GET main.js (no Accept-Encoding) 2 GET main.js Accept-Encoding: gzip 3 main.js Content-Encoding: gzip Cache-Control: max-age=0 ETag: "de158-e58-c7ee4140" 4 main.js Content-Encoding: gzip Cache-Control: max-age=0 ETag: "de158-e58-c7ee4140" 5 main.js Content-Encoding: gzip 1 GET main.js Accept-Encoding: gzip 7 GET main.js If-None-Match: "de158-e58-c7ee4140" Not Modified 9 main.js Content-Encoding: gzip proxy gives browser mismatched content

65 edge case: ETags removed Proxy Origin Server 6 GET main.js (no Accept-Encoding) 2 GET main.js Accept-Encoding: gzip 3 main.js Content-Encoding: gzip Cache-Control: max-age=0 Last-Modified: Thu, 21 Aug :53:57 GMT 4 main.js Content-Encoding: gzip Cache-Control: max-age=0 Last-Modified: Thu, 21 Aug :53:57 GMT 5 main.js Content-Encoding: gzip 1 GET main.js Accept-Encoding: gzip 7 GET main.js If-Modified-Since: Thu, 21 Aug :53:57 GMT 8 main.js Cache-Control: max-age=0 Last-Modified: Fri, 22 Aug :43:15 GMT removing ETags avoids the problem 10 main.js (no gzip) 9 main.js Cache-Control: max-age=0 Last-Modified: Fri, 22 Aug :43:15 GMT

66 edge case fixes Vary: Accept- Encoding Cache-Control: private ETag aol.com x ebay.com xxx (IIS) facebook.com x google.com/search x search.live.com/results xx (IIS) msn.com x (IIS) myspace.com xx (Apa) en.wikipedia.org/wiki x (Apa) yahoo.com x youtube.com xsome Vary: User-Agent – not used March 2007 October 2008

67 questions How much are file sizes typically reduced by using gzip compression? What types of resources (images, scripts, etc.) should not be compressed? For the resource types that should be compressed, should they always be compressed? How do you prevent proxies from serving gzipped resources to browsers that don't support gzip? How can ETags cause proxies to serve mismatched content to browsers?

68 Rule 5: Put Stylesheets at the Top progress indicators: * reassure the system is working convey how much time is left provide something to look at the web page is the progress indicator progressive rendering – draw content as soon as it's available stylesheets block progressive rendering in IE, and cause "flash" in Firefox David Hyatt talks about how browsers work: * Jakob Nielson,

69 stylesheets in IE in IE, nothing in the page is drawn until all stylesheets are done downloading reasoning: parse all rules before drawing any element, avoids having to redraw when stylesheets are at the bottom, there is no progressive rendering => after a long delay the entire page blasts onto the screen

70 IE: fastest feels slowest......and slowest feels fastest stylesheet at bottom: content finishes downloading sooner, but rendering starts later => feels slower stylesheet at top: content finishes downloading later, but rendering starts sooner => feels faster true in IE 6, 7, 8

71 stylesheets in Firefox in Firefox, elements are drawn even if stylesheets aren't all downloaded reasoning: progressive rendering makes the page feel faster (most developers will follow the spec and put their stylesheets in HEAD ?) when stylesheets are at the bottom and they change style of rendered elements, elements have to be redrawn => flash of unstyled content

72 FF2: stylesheets block stylesheets block downloads in Firefox 2 fixed in Firefox 3

73 IE 6,7 and mime filters mime filter plug-ins alter behavior for specific mime types in IE 6,7 mime filters can affect performance

74 resource.cgi formerly sleep.cgi ?type=[gif|js|css|html|swf] &sleep=n – number of seconds &expires=[-1|0|1] – sets Expires header in the past (-1), future (1), or none (0) useful in exaggerating load times making it possible to observe browser behavior

75 questions What is progressive rendering? How do stylesheets affect progressive rendering in IE? in Firefox? Why do they take different approaches? What's the best way to avoid these problems?

76 Rule 6: Put Scripts at the Bottom HTTP spec recommends only two connections (parallel downloads) per hostname results in a stairstep pattern general rule: page load time increases for every two resources added

77 more connections newer browsers open more connections per hostname Chrome 6 Firefox 2 2 Firefox 3 6 IE 6,7 2 IE 8 6 Opera 8 Safari 4 domain sharding – split resources across multiple domains to increase parallelization previous example using two domains browser looks at name, not IP address

78 parallelization is an opportunity for improving load times

79 Rule 6: Put Scripts at the Bottom unfortunately, scripts block in two ways downloading resources below the script rendering elements below the script moving the scripts lower means less blocking

80 challenges document.write scripts that perform document.write must be placed where the content is to be inserted alternative: set element.innerHTML ads ads typically are at the top of the page and include scripts alternative: use iframes or lazy-load ads code dependencies some JavaScript must occur higher in the page, and it depends on other scripts alternative: move scripts as low as possible, combine them

81 parallel script loading execute scripts in order, but download them in parallel with other resources available in IE8, Safari 4, Chrome 2 coming in Firefox 3.5 IE6&7 will be around for years, we have to keep them in mind, so… put scripts at the bottom

82 questions How many connections per hostname is suggested in the HTTP/1.1 spec? Do all browsers follow this recommendation? What's domain sharding? In what way do scripts block a web page? Give a situation where you can't just move a script to the bottom of the page. Which browsers support parallel script loading?

83 how we got here HTTP overview HPWS Rules 1-6 break web 100 HPWS 7-14

84 how we got here HTTP overview HPWS Rules 1-6 break web 100 HPWS 7-14

85 exercise: Web 100 stats 1.pick two web sites 2.put your name in "reviewer name" column (each web site has two reviewers) 3.use Hammerhead to measure total load time (columns B & C) 4.use YSlow to measure the rest 5.if you're the last one finished with a web site and your stats are very different, find the other reviewer and try to resolve

86 how we got here HTTP overview HPWS Rules 1-6 break web 100 HPWS 7-14

87 Rule 7: Avoid CSS Expressions used to set CSS properties dynamically in IE 5-7 fixes IE CSS 2.1 bugs and shortcomings, such as lack of support for min-width min-width: 600px; width: expression( document.body.clientWidth < 600 ? 600px : auto ); problem: expressions execute 1000s of times mouse move, key press, resize, scroll, etc. counter.php (IE only!) expression's JavaScript can slow down pages

88 alternatives to expressions expressions are evaluated all the time (mouse move, etc.), this is what makes them easy but slow alternatives are more work, but reduce the amount of JavaScript code executed alternatives: one-time expressions event handlers

89 one-time expressions if an expression only needs to be calculated once, it can overwrite itself with the value #maindiv { min-width: 600px; width: expression(setW(this));} function setW(elem) { elem.style.runtimeStyle.width = ( document.body.clientWidth < 600 ? "600px" : "auto" ); } doesn't handle window resizing overwrite the expression

90 event handlers tie the code to the specific event(s) of interest #maindiv { min-width: 600px; width: expression(setW(this));} function setW() { elem=document.getElementById('maindiv'); elem.style.runtimeStyle.width = ( document.body.clientWidth < 600 ? "600px" : "auto" ); } window.onresize = setW;

91 Expressions in IE8 expressions are no longer supported in IE8 standards mode reasons: standards compliance – issues fixed in IE8 performance security – "reduce browser attack surface" expressions.aspx but we'll still need to deal with IE6&7 for years to come

92 questions How do CSS expressions affect performance? What are two workarounds to this problem with CSS expressions?

93 Rule 8: Make JS and CSS External Browser Cache Expt: how much are resources cached? part-2/ add transparent pixel image: with specific headers: Expires: Thu, 15 Apr :00:00 GMT Last-Modified: Wed, 22 Oct :49:57 GMT requests from the browser will have one of these response status codes: 200 – the browser does not have the image in its cache 304 – the browser has the image in its cache, but needs to verify the last modified date

94 desired metrics What percentage of users view with an empty cache? # unique users with at least one 200 response total # unique users What percentage of page views are done with an empty cache? total # of 200 responses # of # of 304 responses

95 cache results 40-60% of users/day visit with an empty cache 75-85% of page views/day are primed cache

96 inline or external? var favNumber = 128; OR

Ads by Google