The wackiest phones of the year are now much cheaper in the Argos Black Friday sale
by Tom BedfordThe wackiest phones of the year are now much cheaper in the Argos Black Friday sale
As well as some more conventional devices
Samsung Galaxy A80 (Image credit: Future)
If you're looking for a deal on one of the more ubiquitous best smartphones, there are plenty around right now in the run-up to Black Friday; and if you want a phone that's a little different, to make you stand out from the crowd or just shake up your mobile experience, then there are some special deals just for you.
As part of its early Black Friday sales, Argos has cut the prices of loads of smartphones, including some of the more wacky and weird releases of the year.
These include the Nokia 9 PureView with its five rear cameras, the Samsung Galaxy A80 with its intriguing pop-up camera segment, and the Sony Xperia 10 with its peculiar aspect ratio.
If you're happy with a more conventional phone, Argos has also cut the prices of lots of other handsets.
We've picked our way through the list to find you some of the highlights, including the Honor 20 Pro and Samsung Galaxy A70, two of the best mid-rangers of the year, and the Samsung Galaxy S10, which tops our list of the best smartphones of 2019.
If none of these offers piques your interest, feel free to browse the Argos mobile phones homepage to see if anything else there appeals to you.
TechRadar is scouring every retailer and rounding up all the top deals over the Black Friday period, and we’ve put all the best Black Friday deals and Cyber Monday deals in easy-to-navigate articles to help you find the bargains you’re looking for.


[§39B-3] Application of allocation. The extent to which all or any part of an allocation made to a county or an issuer pursuant to section 39B-2 is applied to a project subject to the annual state ceiling shall be evidenced by a certificate of the issuer or the director of finance of a county, as applicable. [L 1987, c 62, §2]


12.8 Miscellaneous Admin Settings
There are a number of other site administation tabs available.
12.8.0 Statistics
This shows statistics of the system and the database. Note: if there seems to be
memory usage problems (e.g., excessive memory use from a memory leak) you can simply
add a URL argument to any URL:
...?gc=true
(I know its a bit of a hack)
and the caches will be cleared and Java garbage collector will run. Then, you can revisit the statistics
page and see if that has affected the memory usage (it should).
12.8.1 Access
This tab just shows an overview of all of the entry access control settings.
12.8.2 Logs
You can view the last 200 accesses to the repository. Because the googlebot is so
voracious we show its (and other bots) accesses highlighted.
This tab also provides access to the current error log and the full error log.
These two log files are in <ramadda home>/logs. The current log is the log
file for just this current run of the repository. The full log file is a concatenation
of all of the logging.
This view shows the last N lines of the log file. You cann show more or less of the log.
Also, Java stack traces are shown hidden and can be expanded.
12.8.3 Clean Up
This still needs work but the Clean Up facility will go through the
resository database and remove all entries that are file used and the
file does not exist. For example, if you are harvesting a real-time
data archive and adding the entries into the database the files in the archive
may be removed from some external process (e.g., scouring). In this case these
entries will not be shown to a user (RAMADDA checks for that) but they will not
be deleted from the database.
We are intending to do this automatically in the future.
12.8.4 User Message
You can enter a message that is shown to all users one time in this tab.
For example, you could announce that the repository will be unavailable
for a certain time, etc.


Welcome
Dana Iuliana Krizboi
Sample Web Pages
A Tale of Two Cities
IBAT Chocolates
Prince & Purple Rain
Lorem Ipsum Text
Vincent van Gogh
Irish Tourism
Smoothies
Software Analytics
Web Page with Panels
Web Page with Pastel Sections
Web Page with Dark Sections
Web Page with Alternating Sections
Two Column Flexbox Layout
Three Column Flexbox Layout
Four Column Flexbox Layout
Time Flashes


{% extends "layout.html" %} {% block content %} {% if logfail %}
An email has been sent to your mail address, please follow its instructions to enable/access WTime services.
{% endblock %} {% block info %} {% endblock %} {% block scripts %} {{ super() }} {% endblock %}


Thanks to one Mr. @haejin we were able to pull off a $10k market transaction day. Gold foil legendaries sold between $1-2k today. He’s still in the market for a Selenia Sky from what I can tell. There’s a few thousand dollars waiting for someone there that has it. Sorry bud, mine isn’t for sale!
Max Level Common Foils
I also max leveled all my cards. I had all but three commons maxed. As it turns out it’s the three that people were hoarding, but @positive-trail hooked me up. Bought 50 commons of every type for 60 sbd and did that twice. I’m confidant that by years end we’ll have a card delegation model so that investors can rent out cards to players. So, I’m stacking up on cards and looking forward to playing some and renting out a ton.
Turns out that right now there’s only enough common cards in the game for 138 people to have maxed decks when the game starts. Considering there are around 2000 people playing chances are you won’t have enough. Might want to figure that out before we release stats and fighting…
Brushing my gold shoulders off
So, maxxing my common foil cards is now on top of having every gold card in the game.
It hurts my eyes to see that much shiny legendary gold in one spot…
A few days ago I could have thought about maxing out all but the gold legendaries… now I think that’s out of reach. Gonna have to grind it out methinks.
Looking for reps
Anyway, Steem Monsters is cruising along. I’m open to some higher rep Asia based people that would like to talk to their peers about this game. we could use someone that speaks Korean, Japanese, and Chinese (can be three people). If you’re interested and would like a gig where we give you packs to hand out to new players then hit me up in Discord. Pay is weak but the work is fun and you get mega cool points with everyone. You’re like the drug dealer handing out free drugs!
Game Jam
We have our game jam starting on Saturday. I’m hoping for some pretty cool stuff. We’ll see what you all create. If you’re a game creator please check out
Sneaky Contest
Finishing my whole deck feels like a big deal to me. Type “Monster Maverick” in the comments below and upvote and resteem this post. You’re in a drawing for 100 booster packs on me (from aggroed money, not steemmonsters money).
This page is synchronized from the post: ‘So… we have a $10k market day in Steem Monsters land and I maxed leveled all my commons finally’


Several months ago I began using Adrafinil (a 'Nootropic') semi-regularily and am impressed with it. Generally tablets are 300mg however I've found that as little a 50mg has significant (albeit subtle) effect.
I find it stimulating in a way similar to l-deprenyl but a bit more intense(IMO). Concentration, 'speed of thought', memory, energy, motivation, and alertness are all significantly increased for about 4-6 hrs. Mood is mildly elevated. For me there is a hint of anxiety as well but much less so than with 'dirtier' stimulants like caffeine or BZP... Very 'crisp' feeling and as though my brain likes it.
Effects (for me) come on quite 'noticably' about 1/2 hour after ingestion and something of a 'rush' is felt with very mild but pleasurable 'energy waves' detected. This is with 300-900mg.
It is worth noting that I consume absolutely no caffeine, nicotine, or any other stimulants and so am likely a good bit more sensitive to Adrafinil's stimulating properties.
Experience Reports are the writings and opinions of the individual authors who submit them.
Some of the activities described are dangerous and/or illegal and none are recommended by Erowid.


Welcome to the official EDEN fan club!
A list of EDEN's favourite sites
A link to this page!
A link to EDEN's page!
A link to EDEN's YouTube channel!
A link to EDEN's Instagram!


|
Ray Tracer Common Project
Image rendering program based on the ray tracing technique.
|
Load an image from the file system. More...
#include <bitmap.hpp>
Public Member Functions | |
| bitmap (const std::string &filename) | |
| Make a bitmap from a file name. | |
| virtual color | get_pixel (int x, int y) const |
| virtual void | set_pixel (int x, int y, const color &c) |
| Sets a pixel to a given color. | |
| Public Member Functions inherited from rt::image | |
| image (int width, int height) | |
| image (const image &img) | |
| virtual | ~image () |
| virtual int | width () const |
| virtual int | height () const |
| virtual image | copy () const |
| virtual void | blit (image &dst, const rect &srcrect, int dstx, int dsty) const |
| virtual void | blit (image &dst, int dstx, int dsty) const |
| virtual void | draw_line (int x1, int y1, int x2, int y2, const color &c) |
| virtual void | draw_rect (int x1, int y1, int x2, int y2, const color &c) |
| virtual void | fill_rect (int x1, int y1, int x2, int y2, const color &c) |
Additional Inherited Members | |
| Protected Member Functions inherited from rt::image | |
| image () | |
| Protected Attributes inherited from rt::image | |
| SDL_Surface * | data |
Load an image from the file system.
The Bitmap is a 24 or 32 bits image loaded from the file system.
|
virtual |
Reimplemented from rt::image.


{% extends 'partials/layout.html' %} {% block extrastyles %} {% endblock %} {% block content %} Ta-da-app! Had fun? We are looking to add more Tapps to our examples collection. Share yours? {% endblock %} {% block extrascripts %} {% endblock %}
We are looking to add more Tapps to our examples collection. Share yours?


Liverpool reveal controversial plans as Aston Villa Carabao Cup date announced
Latest Aston Villa news | The date for the quarter-final tie has been confirmed and Liverpool have revealed their plans
by Shane Ireland, https://www.facebook.com/ShaneIrelandJournalistAston Villa will host Liverpool in the Carabao Cup quarter-final on Tuesday 17 December (7:45pm kick-off) – and the visitors have revealed controversial plans for the fixture.
The last eight tie has been the subject of a great deal of confusion because of Liverpool’s involvement in the Club World Cup.
The Reds play in the semi-final of the competition in Qatar a day later on the Wednesday and Jurgen Klopp previously threatened to boycott the Villa clash if a suitable arrangement wasn’t reached.
Today (Tuesday) Liverpool have confirmed their intention to play one team at Villa Park and a separate side 24 hours later in Qatar.
Aston Villa latest
This is your round up of all things Villa; the one-stop shop that will keep you updated on the latest goings on at the club and beyond.
Transfers, injuries, match-days and managers, we’ve got you covered.
We’ll be bringing the very latest on the Villains throughout the week and around the clock.
Make sure you keep yourself updated with our handy daily catch up...
Must reads...
- John McGinn's brilliant Liverpool verdict
- James Milner makes Aston Villa transfer admission
- Fans split on Villa transfer target
- Referee confirmed for Wolves v Villa - and fans won't like it
For all your latest AVFC news, opinion, analysis and transfer gossip, click here
You can follow our Villa reporter Ashley Preece on Twitter, and his email address is<EMAIL_ADDRESS>In a statement posted on the club website confirming the settled date of the match at Villa Park, Liverpool wrote: “As a result, we will be utilising two playing squads simultaneously, with one squad participating in the FIFA Club World Cup in Qatar and another in the Carabao Cup.
“The club would like to take this opportunity to underline that while this is not an ideal scenario, it is an outcome which was arrived at with the best interests of the competition, our fellow clubs and ourselves as the sole motivating factor.
“We would like to thank the EFL for their efforts to accommodate us and we can confirm alternative dates were discussed, but ultimately none were considered suitable without compromising the scheduling of the competition itself or placing an undue strain on our playing staff.”
Villa beat Wolves in the fourth round while Liverpool won against Arsenal and Sky Sports have already confirmed the tie will be screened live along with Manchester City’s trip to League One Oxford United.
Villa lost 2-1 against Klopp's side in the Premier League on Saturday, with Trezeguet's opener cancelled out by late goals from Andy Robertson and Sadio Mane.


Creating UNBXD Banners Was Never This Easy!
Guest
MPR
PLCC
Click here to Create JSON
Your Required UNBXD Banner JSON will appear here


The only other operation that we need for our simple example is closing the database. Again, this interface is accessed through a function pointer that is an element of the database handle returned by db_open.
It is necessary that the database be closed. The most important reason for this is that Berkeley DB runs on top of an underlying buffer cache. If the the database is never closed, changes that you have made to the database may never make it out to disk, because they are still in the cache. As the close function, by default, flushes the cache, closing the database will update the on-disk information.
The DB->close interface takes two arguments:
Here's what the code to call DB->close looks like:
#include <sys/types.h> #include <stdio.h> #include <db.h>#define DATABASE "access.db"
int main() { extern int errno; DB *dbp; DBT key, data;
if ((errno = db_open(DATABASE, DB_BTREE, DB_CREATE, 0664, NULL, NULL, &dbp)) != 0) { fprintf(stderr, "db: %s: %s\n", DATABASE, strerror(errno)); exit (1); }
memset(&key, 0, sizeof(key)); memset(&data, 0, sizeof(data)); key.data = "fruit"; key.size = sizeof("fruit"); data.data = "apple"; data.size = sizeof("apple");
switch (errno = dbp->put(dbp, NULL, &key, &data, 0)) { case 0: printf("db: %s: key stored.\n", (char *)key.data); break; default: fprintf(stderr, "db: put: %s\n", strerror(errno)); exit (1); }
switch (errno = dbp->get(dbp, NULL, &key, &data, 0)) { case 0: printf("db: %s: key retrieved: data was %s.\n", (char *)key.data, (char *)data.data); break; case DB_NOTFOUND: printf("db: %s: key not found.\n", (char *)key.data); break; default: fprintf(stderr, "db: get: %s\n", strerror(errno)); exit (1); }
switch (errno = dbp->del(dbp, NULL, &key, 0)) { case 0: printf("db: %s: key was deleted.\n", (char *)key.data); break; default: fprintf(stderr, "db: del: %s\n", strerror(errno)); exit (1); }


Sum array elements greater than a value
Write a program that reads an array of ints and an integer number
n
. The program must sum all the array elements greater than
n
.
Input data format
The first line contains the size of an array.
The second line contains elements of the array separated by spaces.
The third line contains the number
n.
Output data format
Only a single number representing the sum.
Sample Input:
5
5 8 11 2 10
8
Sample Output:
21
Sample Input:
4
-5 -4 -6 -11
-10
Sample Output:
-15
Memory limit: 256 MB
Time limit: 8 seconds
Show topic summary


🏃 Loading...
Enter details of a book to add to library
✅ Save
🔍 Search library
Search among the books in library
search by


{% extends "bmsapp/base.html" %} {% block pagetitle %}Ecobee Authorization{% endblock %} {% block this_nav_link %}{% endblock %} {% block title %}Ecobee Thermostat Authorization{% endblock %} {% block content %}
This page is used to obtain authorization "keys" that allow BMON to access data from your Ecobee thermostat account. BMON only reads data from the thermostats and does not issue any commands to change the thermostat settings or operation.
Authorize the BMON application to read thermostat data from your Ecobee account by using the following PIN. Do this by logging in to your Ecobee account and use the drop-down menu in the upper right corner. Select "My Apps" and then click the "Add Application" button. Follow instructions from there. You have {{ expires_in }} minutes to complete this task before moving to Step 2.
Enter this PIN (capitalization is important): {{ ecobeePin }}


API endpoints¶
Authentication¶
/accounts/login/ (POST)
- password
Return a dictionary of values used to paste success or error status
- Sucess Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- notify_stat - returns a boolean either true or false ..note::
This should be used to determine if user wants to set notication on or off
- Error Response
- message - False
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- notify_stat - returns a boolean either true or false ..note::
This should be used to determine if user wants to set notication on or off
..note::
FOR EACH RESPONSE THE STATUS CODE IS ALWAYS 200
- /accounts/logout/ (GET)
Response
/accounts/password-reset/email/ (POST)
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- token - a unique token generated
- Error Response
- message - False/Email doesnt belong to any account
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/password-reset/number/ (POST)
- number
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- token - a unique token generated
- Error Response
- message - False/number not found
- res - false ..note::
A BOOLEAN VALUE
reason - A value paste to the user
..note::
An OTP code will sent to user phone number after calling /accounts/password-reset/number/
/accounts/password-reset/resend-email/ (GET)
/accounts/password-reset/confirm-email-code/ (POST)
- password
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- Error Response
- message - False/Failed
- res - false .. note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/resend-otp-code/ (GET)
A query string of reset_token
Note
When calling this url you must paste the reset_token as a query string that was generated in the reset password number API
-Example on how to paste the query params
—https://accounts/password-reset/resend-otp-code/?reset_token=’token’
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- Error Response
- message - expired token/code not sent
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/password-reset/confirm-code/ (POST)
-otp_code
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- Error Response
- message - expired token/code not sent
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/password-resent/confirm/ (POST)
-email_code
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- Error Response
- message - expired token/code not sent
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
– /accounts/resend-otp-code/ (GET)
A query string of token
Note
When calling this url you must paste the token as a query string that was generated in the reset password number API
-Example on how to paste the query params
—https:accounts/password-reset/resend-otp-code/?token=’token’
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- Error Response
- message - expired token/code not sent
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
Registration¶
/accounts/customer-signup/ (POST)
- full_name
- contact_address –optional
- user {email:user email, password:user password, contact_number:user contact_number}
- Example on how this endpoint should be called with post params
content = {full_name: user full_name, contact_address: user contact_address, user {email: user email, password: user password, contact_mumberr: user contact_number}}
..note::
ALWAYS SET THE REQUEST HEADER T0 CONTENT-TYPE-APPLICATION/JSON
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- token - token
- Error Response
- message - False/number exist/invalid number
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/owner-signup/ (POST)
- full_name
- contact_address –optional
- profile_picture –required
- gender – a choice field with the option of a male or female
- user {email:user email, password:user password, contact_number:user contact_number}
- Example on how this endpoint should be called with post params
content = {full_name: user full_name, contact_address: user contact_address,
- ‘gender’: male or female,
user {email: user email, password: user password, contact_number: user contact_number}}
..note::
ALWAYS SET THE REQUEST HEADER T0 CONTENT-TYPE-APPLICATION/JSON
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- token - token
- Error Response
- message - False/number exist/invalid number
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/agent-signup/ (POST)
this endpoint details would be used for the following user types(developer, government, supplier, valuer)
- full_name
- contact_address –optional
- profile_picture –required
- cac_number - cac reg no –required
- office_address - user work address –optional
- work_identity - user id card or work id –required
- user {email:user email, password:user password, contact_number:user contact_number}
- Example on how this endpoint should be called with post params
content = {full_name: user full_name, contact_address: user contact_address, profile_picture: user photo, office_address: user office address, work_id: work identity or personal identity, user {email: user email, password: user password, contact_number: user contact_number}}
..note::
ALWAYS SET THE REQUEST HEADER T0 CONTENT-TYPE-APPLICATION/JSON
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- token - token
- Error Response
- message - False/number exist/invalid number
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/developer-signup/ (POST)
/accounts/supplier-signup/ (POST)
/accounts/government-signup/ (POST)
/accounts/valuer-signup/ (POST)
/accounts/hotel-signup/ (POST)
- address - hotel address–optional,
- full_name - full_name
- profile_picture -required
- hotel_name - required
- hotel_website - optional
- cac_number - cac reg no –required
- work_identity - user id card or work id –required
- user {email:user email, password:user password, contact_number:user contact_number}
- Example on how this endpoint should be called with post params
content = {full_name: user full_name, contact_address: user contact_address, hotel_name: hotel name, hotel_website: https://example.com , cac_number: cac reg no,
- work_identity: work id or personal id, profile_picture: profile_picture,
user {email: user email, password: user password, contact_number: user contact_number}}
..note::
ALWAYS SET THE REQUEST HEADER T0 CONTENT-TYPE-APPLICATION/JSON
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- token - token
- Error Response
- message - False/number exist/invalid number
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
/accounts/institute-signup/ (POST)
- institute - institute address–optional
- profile_picture – required
- institute_name – required
- institute_website - optional
- cac_number - cac reg no – required
- work_identity - user id card or work id – required
- user {email:user email, password:user password, contact_number:user contact_number}
- Example on how this endpoint should be called with post params
content = {full_name: user full_name, contact_address: user contact_address, institute_name: institute name, institute_website: https://example.com , cac_number: cac reg no, work_identity: work id or personal id, profile_picture: profile_picture, user {email: user email, password: user password, contact_number: user contact_number}}
..note::
ALWAYS SET THE REQUEST HEADER T0 CONTENT-TYPE-APPLICATION/JSON
- Success Response
- message - True
- res - true ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
- token - token
- Error Response
- message - False/number exist/invalid number
- res - false ..note::
A BOOLEAN VALUE
- reason - A value paste to the user
..note:: FOR WEB CLIENT IF ANY CSRF ISSUE OCCURS PLEASE FOLLOW THE STEP BELOW TO RESOLVE IT
After successfully logging the user a csrf token would be returned in the request header, this csrf token would be needed to make any further post or put request to any authenticated view, that is to make any post request you would need to set the token in the request header like this ``header = {Content-Type:application/json, X-CSRFToken: csrf token here} ``


%include file="header.html" args="page='/config/global', subtitle='Server Configuration'"/>
These settings apply to all transfers belonging to the same Virtual Organization.


Content in upper table (table with 1 row) will be shifted to the left after dragging element from upper to the bottom table. Inside event.dropped() handler is JS code that will be executed only in case of dragging from table 0 to table 1. All other movements are ignored.


| Alias: | |
| Description: | |
| Balance: | |
| Debit Limit: | |
| Credit Limit: | |
| Intertrade: | |
| Commune: |
| Name: | |
| Description: | |
| Group ID: | |
| Number of Members: | |
| Currency: | |
| Rate: | |
| Default Debit: | |
| Default Credit: | |
| Open: | |
| Commune account: | |
| Intertrade Account:: | |
| Intertrade Balance: | |
| Intertrade Debit: | |
| Intertrade Credit: |


lxde-logout -program for...
lxde-logout
This manual page explains the lxde-logout program. This program is part of LXDE.
lxde-logout, as its name implies, is for logging out LXDE. Running this program will show the logout dialog on the screen.
startlxde
Hong Jen Yee <pcman.tw@gmail.com>


Who the Heck Is Fai Khadra, Kendall Jenner’s Bestie and Bieber Wedding Date?
Okay, be honest: You probably clicked on this article because you’re dying to get alllll the details on who TF Kendall Jenner’s new man-friend Fai Khadra is. I can’t blame you, because it kinda just seems like Fai is always randomly popping up on your feed and you have no clue why. This feeling of, Who the heck is this man? heightened for literally EVERYONE when Kendall decided to take Fai as her date to Justin Bieber and Hailey Baldwin’s gorgeous wedding and later Instagrammed about starting a family with him. 😳
Since you’re itching for information, here is everything you need to know about Fai, your newest man crush and ultimate favorite of the Kar-Jenner clan—you’re welcome.
He is really good friends with Kendall.
Uhhh, sorry to break it to you guys, but Fai and Kendall probably aren’t dating. There’s always the teeniest of possibilities, but for the most part, it looks like Kendall and Fai are just really good friends. (Even though she joked about starting a whole family with him this week, to the delight of literally all her friends and family, but I digress.)
Kendall and Fai are so close that they dressed up for Halloween together last year—Kendall went as a Fembot, while Fai hilariously dressed up as Austin Powers.
Fai also went with Kendall to the Wimbledon men’s final in London over the summer, and they had a super-cute twinning moment!
And of course, lest we forget the famous Bieber wedding, where Kendall brought Fai as her date and literally said, “We don’t date, he’s just my date.” Peep how lovely they looked!
He was previously linked to Jordyn Woods and Kourtney Kardashian.
Fai was spotted holding hands with Jordyn Woods back in 2017, thus sparking rumors of a romance. It didn’t really go anywhere else besides this one picture:
Jordyn also made an appearance on his page last year (alongside Kendall) at the annual Coachella Music Festival.
And as far as Kourtney, Fai was seen enjoying some quality time with her shortly after she broke up with Younes Bendjima. But much like things between him and Kendall, it was most likely another platonic situation with Kourtney.
He has two twin sisters.
His sisters’ names are Sama and Haya, but have you ever heard of the DJ duo SIMIHAZE? Yep, that’s Fai’s sisters. How freakin’ cool is that?
He models and makes music.
 The man is certified attractive, which is probably how he finds himself surrounded by other attractive individuals like the Kar-Jenners. Fai also sings and does a little DJ’ing here and there but hasn’t posted any songs in three years. His work can be found on SoundCloud.
He has a PUPPY!!!
And a damn cute one, to say the least!
Related Story
Kendall Jenner Was Seen With Fai Khadra...Again
Starr Bowenbank Editorial Assistant Starr Bowenbank is the editorial assistant who writes about all things pertaining to news, pop culture, and entertainment—you can follow her here.


{% extends "base.html" %} {% block title %}
This part of the app has not yet been implemented, sorry 😢
{% if text %}In the mean time, here is what we've done with your data so far:
{{ text }}
{% else %}
We didn't receive any form input.
{% endif %}


| Committee | Country | Delegate Name | Delegate Grade level | Delegate School |
|---|---|---|---|---|
| {{assignment.committee.name}} | {{assignment.country}} | {% if assignment.delegate %} {{assignment.delegate.name}} {% endif %} | {% if assignment.delegate %} {{assignment.delegate.grade}} {% endif %} | {% if assignment.delegate.teacher %} {{assignment.delegate.teacher.school}} {% endif %} |


Our mission
At H & O Granite Kitchens, we specialise in fitting granite kitchen countertops.
Over the years we have developed an excellent track record of excellence. We have helped transform the kitchens of several homes in Gauteng.
Feel free to contact us to learn about why granite kichen counters are good for you and also to get a quotation.


They say pizza begun as basic flatbreads with several toppings.
Fancy to note, a precursor of pizza was probably the focaccia. Pizza was mainly eaten in italy and by emigrants from there.
Here at Pizza Mojo, we guarantee to offer the best satisfaction as we have a wide variety of our signature culinary delight to suit every palette.
Therefore, feel free to scroll along, make a choice (or choices) and we will be sure to take your order and serve you to your full expectation.
Our main mission and purpose here at Pizza Mojo is to deliver the best services in form of our scrumptious pizza to our customers.
As a highly fledged restaurant, it is our sole endeavour to ensure that everyone leaves this place satisfied and asking for more
Thus is the reason we have a wide variety of pizza to choose from, all for you.
Our vision is to become a leading restaurant especially in the pastry section.
As is the reason that we highly value feedback from our clientele, to improve and add onto our existing skills.
Visitors.
Prizes.
Happy Clients
Compliments.


We want to here from you. Whether it is a question or feedback, we want to know your thoughts on how we are doing. Feel free to contact us using one of the methods below.


Class FocusEventDispatchExtensions
Focus event dispatch helper extension methods.
Inheritance
Namespace: Bunit
Assembly: Bunit.dll
Syntax
public static class FocusEventDispatchExtensions : object
Methods
| Improve this Doc View SourceBlur(IElement, FocusEventArgs)
Raises the @onblur
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static void Blur(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
Blur(IElement, Nullable<String>)
Raises the @onblur
event on element
, passing the provided
properties to the event handler via a
Declaration
public static void Blur(this IElement element, string? type = null)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| System.Nullable<System.String> | type | The type of the event. |
BlurAsync(IElement, FocusEventArgs)
Raises the @onblur
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static Task BlurAsync(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
Returns
| Type | Description |
|---|---|
| Task | A task that completes when the event handler is done. |
Focus(IElement, FocusEventArgs)
Raises the @onfocus
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static void Focus(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
Focus(IElement, Nullable<String>)
Raises the @onfocus
event on element
, passing the provided
properties to the event handler via a
Declaration
public static void Focus(this IElement element, string? type = null)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| System.Nullable<System.String> | type | The type of the event. |
FocusAsync(IElement, FocusEventArgs)
Raises the @onfocus
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static Task FocusAsync(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
Returns
| Type | Description |
|---|---|
| Task | A task that completes when the event handler is done. |
FocusIn(IElement, FocusEventArgs)
Raises the @onfocusin
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static void FocusIn(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
FocusIn(IElement, Nullable<String>)
Raises the @onfocusin
event on element
, passing the provided
properties to the event handler via a
Declaration
public static void FocusIn(this IElement element, string? type = null)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| System.Nullable<System.String> | type | The type of the event. |
FocusInAsync(IElement, FocusEventArgs)
Raises the @onfocusin
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static Task FocusInAsync(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
Returns
| Type | Description |
|---|---|
| Task | A task that completes when the event handler is done. |
FocusOut(IElement, FocusEventArgs)
Raises the @onfocusout
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static void FocusOut(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
FocusOut(IElement, Nullable<String>)
Raises the @onfocusout
event on element
, passing the provided
properties to the event handler via a
Declaration
public static void FocusOut(this IElement element, string? type = null)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| System.Nullable<System.String> | type | The type of the event. |
FocusOutAsync(IElement, FocusEventArgs)
Raises the @onfocusout
event on element
, passing the provided
event arguments to the event handler.
Declaration
public static Task FocusOutAsync(this IElement element, FocusEventArgs eventArgs)
Parameters
| Type | Name | Description |
|---|---|---|
| IElement | element | The element to raise the event on. |
| FocusEventArgs | eventArgs | The event arguments to passs the event handler. |
Returns
| Type | Description |
|---|---|
| Task | A task that completes when the event handler is done. |


Apples
Grapefruits
Bananas
Cranberries
Pineapples
Strawberries
Cherries
Pears
Plums
Apricots
Grapes
Lemons


Problem¶
-
class
flopt.problem.
Problem
(name=None, sense='minimize')[source]¶ Interface between User and Solver
- Parameters
name (str) -- name of problem
sense (str, optional) -- minimize or maximize
obj (Expression or CustomObject) -- objective function
variables (set of VarElement family) -- variables
time (float) -- solving time
prob_type (list of str) -- type of problems
Examples
>>> prob = Problem(name='test')
When we want to solve the maximize problem, then
>>> prob = Problem(name='test', sense='maximize')
Input solver, when we solve
>>> solve = Solver(algo=...) >>> prob.solve(solver=solver, timelimit=10)
After solving, we can obtain the objective value.
>>> prob.getObjectiveValue()
-
setObjective
(obj)[source]¶ set objective function
- Parameters
obj (VarElement family, Expression or CustomObject) -- objective functioon
-
solve
(solver=None, timelimit=None, msg=False)[source]¶ solve this problem; objective function is self.obj; variables is self.variables
- Parameters
solver (Solver) -- solver
timelimit (float) -- timelimit
msg (bool) -- if true, display the message from solver
- Returns
int -- return the status of solving
Log -- return log object
-
class
flopt.problem.
ObjectiveFunction
(obj)[source]¶ Objective Function for Solver
ObjectiveFunction is an overwrap class for expression and customObjects. In this class, by specifying the value argument as a solution, we can compute the objective value of the solution.
- Parameters
obj (Expression or CustomObject) -- objective function
type (str) -- 'Expression' or 'CustomObject'


1)
The
was just delivered by the postman.
mail
mail,
male
2)
Amy got the lead
in the school’s Annual Day play.
role
role,
roll
3)
He does not
the answer.
know
no,
know
4)
Tom is going to
his friend at 5 o’clock.
meet
meat,
meet
5)
The referee blew his whistle for a
.
foul
foul,
fowl
6)
This
is sweet and juicy.
pear
pear,
pair
7)
The boy looked
when the principal called him.
pale
pail,
pale
8)
Rob narrated the
of his jungle adventure.
tale
tale,
tail


There’s nothing greater in the world than when somebody on the team does something good, and everybody gathers around to pat him on the back No matter how brilliant your mind or strategy, if you’re playing a solo game, you’ll always lose out to a team


Uploading Img...
Upload your image
File should be Jpeg, Png, ...


{% extends 'layouts/layout-bsa-design-system.html' %} {% block pageTitle %} Start page template - NHS.UK prototype kit {% endblock %} {% block beforeContent %} {% endblock %} {% block content %}
Patterns are guidance on how to solve certain types of problem. They include examples of successful approaches but are flexible and don’t prescribe exact design solutions.
These patterns extend those used across Government in the GOV.UK design system. As our patterns mature, we will look to contribute them to the GOV.UK system so they can be used more widely.


Description
The world is at your feet with all the eyes set on you; but here you face it alone, for
everybody may not be able to get to this height. Forget about simultaneity and
formation. Keeping the spirits high, cool your heels, it's time to step up a notch at
VIVACITY-2020’s solo dance competition. Spin and sway, prance and engross all in
the rhythm of cosmic tune and divine music. The heart beats have gone up and
everyone is eager to sink in the captivating poetry of footsteps.
1st Round:
1. This is a Solo Western Competition.
2. Time limit: Max: 2 min, Min: 1 min. 30 sec. Candidates are required to
strictly adhere to the time limits.
3. All forms of western dance are allowed (Classical dance form is not
allowed).
4. Get two copies of your song- one on a CD and one in pen drive.
5. Song should be submitted one hour prior to the prelims.
2nd Round (Prop round):
1. The shortlisted participants from the 1st Round will be given some props by
us and will perform on the song of their choice (the song should be
available).
2. Time limit: Max: 2 min, Min: 1 min 30 sec. Time limit should not be
exceeded.
3. Candidates must bring two copies of their songs as stated earlier, i.e., on a
CD and in a pen drive
__
NOTE:
1. Songs should be in .mp3 format. No audio cassettes will be allowed.
2. Tracks should be named as: PIU-your_name-round_name-(PRELIMS/FINALS).
e.g., PIU-dheeraj_agrawal-PRELIMS & PIU-dheeraj_agrawal-FINALS


Dogfinder is an online, searchable database of animals who need homes. It is also a directory of nearly 8,000 animal shelters and adoption organizations across the United States. Organizations maintain their own home pages and available-dog databases.
To use Internet technology and the resources it can generate to:
From the comfort of their personal computers, dog lovers can search for a dog that best matches their needs.
Dogfinder is updated DAILY.
Dogfinder is only a directory of homeless dog and dog adoption organizations. No information in Dogfinder is guaranteed. Although the information on Dogfinder is updated frequently, it is always best to call the facility or organization that lists the dog to insure it is still available and to insure the information listed in Dogfinder is accurate. It is crucial that any dog found through an adoption service be thoroughly examined by a veterinarian immediately upon adoption. Any dog found, adopted through, or listed in Dogfinder are the sole responsibility of the adoption organizations and/or the adopting party.
Dogfinder accepts no responsiblity for any liability or for any injury or damages to any person or property caused by any listed dog, as well as any cause of action, claims, suits or demands whatsoever that may arise as a result of such injury or damage.


Strings¶
Essentials¶
Miller string literals are always written with double quotes, like "abcde"
; single quotes
are not part of the grammar of Miller's programming language.
Single quotes are used for wrapping put
/filter
statements, as in mlr put '$b=$a.".suffix"' myfile.csv'
:
the single-quotes are consumed by the shell and Miller gets $b=$a.".suffix"
. (See however the
Miller on Windows page.)
A basic string operation is the .
(concatenation) operator:
mlr --c2p --from example.csv put '$output = $color . ":" . $shape'
color shape flag k index quantity rate output yellow triangle true 1 11 43.6498 9.8870 yellow:triangle red square true 2 15 79.2778 0.0130 red:square red circle true 3 16 13.8103 2.9010 red:circle red square false 4 48 77.5542 7.4670 red:square purple triangle false 5 51 81.2290 8.5910 purple:triangle red square false 6 64 77.1991 9.5310 red:square purple triangle false 7 65 80.1405 5.8240 purple:triangle yellow circle true 8 73 63.9785 4.2370 yellow:circle yellow circle true 9 87 63.5058 8.3350 yellow:circle purple square false 10 91 72.3735 8.2430 purple:square
Also see the list of string-related built-in functions.
1-up indexing¶
The most important difference between Miller's strings and strings in other languages is that indices start with 1, not 0. (The same is true for Miller arrays.) This is intentional.
1-up indices may feel like a thing of the past, belonging to Fortran and Matlab, say; or R and Julia as well, which are more modern. But the overall trend is decidedly toward 0-up. This means that if Miller does 1-up indices, it should do so for good reasons.
Miller strings are indexed 1-up simply because Miller arrays are indexed 1-up. See this section for the reasoning.
Strings have been in Miller since the beginning, but they weren't accessible
using indices or slices until Miller 6. Also, the
substr
function predates Miller
6. This function was implemented to take 0-up indices. When Miller 6 was
implemented, this became inconsistent. As a result, there are
substr0
and
substr1
functions. For backward
compatibility with existing Miller scripts, substr
is the same as substr0
.
But users starting out with Miller 6 will probably want substr1
.
Negative-index aliasing¶
Imitating Python and other languages, you can use negative indices to read
backward from the end of the string, while positive indices read forward from
the start. If a string has length n
then -n..-1
are aliases for 1..n
,
respectively; 0 is never a valid string index in Miller.
mlr -n put ' end { x = "abcde"; print x[1]; print x[-1]; print x[1:2]; print x[-2:-1]; } '
a e ab de
Slicing¶
Miller supports slicing using [lo:hi]
syntax. Either or both of the indices
in a slice can be negatively aliased as described above. Unlike in Python,
Miller string-slice indices are inclusive on both sides: x[3:5]
means x[3] . x[4] . x[5]
.
mlr -n put ' end { x = "abcde"; print x[3:4]; print x[:2]; print x[3:]; print x[1:-1]; print x[2:-2]; } '
cd ab cde abcde bcd
Out-of-bounds indexing¶
Out-of-bounds index accesses are errors, but out-of-bounds slice accesses result in trimming the indices, resulting in a short string or even the empty string. (This behavior intentionally imitates Python.)
mlr -n put ' end { x = "abcde"; print x[1]; print x[5]; print x[6]; } '
a e (error)
mlr -n put ' end { x = "abcde"; print "\"" . x[1:2] . "\""; print "\"" . x[1:6] . "\""; print "\"" . x[10:20] . "\""; } '
"ab" "abcde" ""
Escape sequences for string literals¶
You can use the following backslash escapes for strings such as between the double quotes in contexts such as mlr filter '$name =~ "..."'
, mlr put '$name = $othername . "..."'
, mlr put '$name = sub($name, "...", "...")
, etc.:
\a
: ASCII code 0x07 (alarm/bell)\b
: ASCII code 0x08 (backspace)\f
: ASCII code 0x0c (formfeed)\n
: ASCII code 0x0a (LF/linefeed/newline)\r
: ASCII code 0x0d (CR/carriage return)\t
: ASCII code 0x09 (tab)\v
: ASCII code 0x0b (vertical tab)\\
: backslash\"
: double quote\123
: Octal 123, etc. for\000
up to\377
\x7f
: Hexadecimal 7f, etc. for\x00
up to\xff
\u2766
,\U00010877:
: Unicode literals. For technical reasons, you must supply four hex digits after\u
and eight hex digits after\U
.
mlr repl
[mlr] "a\nb" "a b" [mlr] "a\tb" "a b" [mlr] "a\x62c" "abc" [mlr] "\u2766\U00010877" "❦𐡷"
See also https://en.wikipedia.org/wiki/Escape_sequences_in_C.
These replacements apply only to strings you key in for the DSL expressions for filter
and put
: that is, if you type \t
in a string literal for a filter
/put
expression, it will be turned into a tab character. If you want a backslash followed by a t
, then please type \\t
.
However, these replacements are done automatically only for string literals within DSL expressions -- they are not done automatically to fields within your data stream. If you wish to make these replacements, you can do (for example) mlr put '$field = gsub($field, "\\t", "\t")'
. If you need to make such a replacement for all fields in your data, you should probably use the system sed
command instead.


{% extends 'lib/base.html' %} {% load staticfiles%} {% block content %}
{{ libGroup.description | linebreaks }}
View {{ libGroup.title }} Library »
DESCRIPTION LOCATION TAKEN CATEGORY TIME-CREATED


Hillary Clinton: I'm Facing 'Enormous Pressure' to Enter 2020 Race -- I Want to 'Retire' Trump | Breitbart
by Pam KeyOn Tuesday, former Secretary of State and 2016 Democratic presidential nominee Hillary Clinton told BBC Radio 5 Live she was under “enormous pressure” to consider a 2020 White House run.
When asked about her future in politics, Clinton said, “I feel a sense of responsibility partly because you know my name was on the ballot, I got more votes, but ended up losing to the current incumbent in the White House who I think is really undermining our democracy in very fundamental ways. And I want to retire him.”
When asked if she is absolutely ruling out a 2020 run, Clinton said, “I, as I say never, never, never say never. I will certainly tell you. I’m under enormous pressure from many, many, many people to think about it. But as of this moment, sitting here in this studio talking to you, that is absolutely not in my plans.”
Follow Pam Key on Twitter @pamkeyNEN


Have you ever had an argument about whether a celebrity is dead or alive?
START PLAYINGCelebrity Dead or Alive is an entertainment app to test your knowledge of celebrities who’ve come and gone or still kickin’!
Play against your friends and family and get ready to test your skills with our timed quiz!


Boxing Helena (1993) no stars A movie review by Serdar Yegulalp Copyright 1998 by Serdar Yegulalp
BOXING HELENA is a real act of nerve. It takes the two very worst kinds of movies -- the Pretentious Art Bore and the Ludicrous Cheat -- and synthesizes them. Which is not what the filmmakers were thinking, of course, but there you are.
The movie is the product of Jennifer Lynch, who has the distinction of being director David Lynch's daughter. On the basis of the evidence, talent skipped a generation in the Lynch dynasty. The movie is painfully like a bad Lynch movie -- or better to say, like a bad imitation that didn't have the initative to at least be a parody. It also speaks well for Kim Basinger, who sued to be let out of the production, and has since gone on to redeem herself in the majestic L.A. CONFIDENTIAL, among other things.
Julian Sands stars as Dr. Nick Cavanaugh. We know he's a cold-hearted snot because he walks away from his dad's coffin before it's lowered all the way into the ground. He is a surgeon who specializes in putting arms and legs back on, and based on what little we know about the movie, we can also guess (correctly) that he has a thing for yanking 'em off as well. Sands plays Cavanaugh as the kind of oaf who has women "accidentally" set fire to his clothing at parties.
Nick grows obsessed with a woman named Helena (Sherilyn Fenn), an insufferably irritating person. In an attempt to make her sexy and mysterious, the filmmakers stoop to ripping off LA DOLCE VITA and have her bathe in the fountain at a party. Yes.  Just to make sure the point is hammered brutally home, we constantly get insert shots of the armless Venus DeMilo sculpture. And shots of a bird in a cage. Yes.
Is there a good story lurking anywhere in this? Maybe. Part of the problem lies in the fact that obsession alone does not make a character interesting. Neither Nick nor Helena are the kind of people that would be worth five minutes at a lunch counter, and so no matter what weird paces they get put through, no one's going to care. Movies about obsessions that the viewers do not share are hard to make, unless you make it satirical (as did CRASH) or at least try to convey something of what the obsession means personally (as did LOLITA).
 Which, if you don't know what that refers to, you're encouraged to find out.
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


| TEP: | 1 |
|---|---|
| Group: | Core Working Group |
| Type: | Best Current Practice |
| Status: | Final |
| TinyOS-Version: | All |
| Author: | Philip Levis |
Note
This document specifies a Best Current Practices for the TinyOS Community, and requests discussion and suggestions for improvements. Distribution of this memo is unlimited.
This memo describes the structure all TinyOS Extension Proposal (TEP) documents follow, and defines the meaning of several key words in those documents.
In order to simplify management, reading, and tracking development, all TinyOS Extension Proposals (TEPs) MUST have a particular structure. Additionally, to simplify development and improve implementation interoperability, all TEPs MUST observe the meaning of several key words that specify levels of compliance. This document describes and follows both.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in TEP 1.
Note that the force of these words is modified by the requirement level of the document in which they are used. These words hold their special meanings only when capitalized, and documents SHOULD avoid using these words uncapitalized in order to minimize confusion.
MUST: This word, or the terms "REQUIRED" or "SHALL", mean that the definition is an absolute requirement of the specification.
MUST NOT: This phrase, or the phrase "SHALL NOT", mean that the definition is an absolute prohibition of the specification.
SHOULD: This word, or the adjective "RECOMMENDED", mean that there may exist valid reasons in particular circumstances to ignore a particular item, but the full implications must be understood and carefully weighed before choosing a different course.
SHOULD NOT: This phrase, or the phrase "NOT RECOMMENDED" mean that there may exist valid reasons in particular circumstances when the particular behavior is acceptable or even useful, but the full implications should be understood and the case carefully weighed before implementing any behavior described with this label.
MAY: This word, or the adjective "OPTIONAL", mean that an item is truly optional. One implementer may choose to include the item because a particular application requires it or because the implementer feels that it enhances the system while another implementer may omit the same item. An implementation which does not include a particular option MUST be prepared to interoperate with another implementation which does include the option, though perhaps with reduced functionality. In the same vein an implementation which does include a particular option MUST be prepared to interoperate with another implementation which does not include the option (except, of course, for the feature the option provides.)
Imperatives of the type defined in this memo must be used with care and sparingly. In particular, they MUST only be used where it is actually required for interoperation or to limit behavior which has potential for causing harm (e.g., limiting retransmissions) For example, they must not be used to try to impose a particular method on implementors where the method is not required for interoperability.
TEPs have two major parts, a header and a body. The header states document metadata, for management and status. The body contains the content of the proposal.
All TEPs MUST conform to reStructuredText standards [1] and follow the docutils template, to enable translation from reStructuredText to HTML and LaTeX.
The TEP header has several fields which MUST be included, as well as others which MAY be included. The TEP header MUST NOT include headers which are not specified in TEP 1 or supplementary Best Current Practice TEPs. The first six header fields MUST be included in all TEPs, in the order stated here.
The first field is "TEP," and specifies the TEP number of the document. A TEP's number is unique.. This document is TEP 1. The TEP type (discussed below) determines TEP number assignment. Generally, when a document is ready to be a TEP, it is assigned the smallest available number. BCP TEPs start at 1 and all other TEPs (Documentary, Experimental, and Informational) start at 101.
The second field states the name of the working group that produced the document. This document was produced by the Core Working Group.
The third field is "Type," and specifies the type of TEP the document is. There are four types of TEP: Best Current Practice (BCP), Documentary, Informational, and Experimental. This document's type is Best Current Practice.
Best Current Practice is the closest thing TEPs have to a standard: it represents conclusions from significant experience and work by its authors. Developers desiring to add code (or TEPs) to TinyOS SHOULD follow all current BCPs.
Documentary TEPs describe a system or protocol that exists; a documentary TEP MUST reference an implementation that a reader can easily obtain. Documentary TEPs simplify interoperability when needed, and document TinyOS service implementations.
Informational TEPs provide information that is of interest to the community. Informational TEPs include data gathered on radio behavior, hardware characteristics, other aspects of TinyOS software/hardware, organizational and logistic information, or experiences which could help the community achieve its goals.
Experimental TEPs describe a completely experimental approach to a problem, which are outside the TinyOS core and will not necessarily become part of it. Unlike Documentary TEPs, Experimental TEPs may describe systems that do not have a reference implementation.
The fourth field is "Status," which specifies the status of the TEP. A TEP status can either be "Draft," which means it is a work in progress, "Final," which means it is complete and will not change. Once a TEP has the status "Final," the only change allowed is the addition of an "Obsoleted By" field.
The "Obsoletes" field is a backward pointer to an earlier TEP which the current TEP renders obsolete. An Obsoletes field MAY have multiple TEPs listed. For example, if TEP 191 were to replace TEPs 111 and 116, it would have the field "Obsoletes: 111, 116".
The "Obsoleted By" field is added to a Final TEP when another TEP has rendered it obsolete. The field contains the number of the obsoleting TEP. For example, if TEP 111 were obsoleted by TEP 191, it would have the field "Obsoleted By: 191".
"Obsoletes" and "Obsoleted By" fields MUST agree. For a TEP to list another TEP in its Obsoletes field, then that TEP MUST list it in the Obsoleted By field.
The obsoletion fields are used to keep track of evolutions and modifications of a single abstraction. They are not intended to force a single approach or mechanism over alternative possibilities.
If a TEP is Best Current Practices or Documentary, then it MUST include an additional field, "TinyOS-Version:," which states what version(s) of TinyOS the document pertains to. This document pertains to all versions of TinyOS, until made obsolete by a future TEP. This field MUST appear after the Status field and before the Author field.
The final required field is "Author," which states the names of the authors of the document. Full contact information should not be listed here (see Section 3.2).
There is an optional field, "Extends." The "Extends" field refers to another TEP. The purpose of this field is to denote when a TEP represents an addition to an existing TEP. Meeting the requirements of a TEP with an Extends field requires also meeting the requirements of all TEPs listed in the Extends field.
If a TEP is a Draft, then four additional fields MUST be included: Draft-Created, Draft-Modified, Draft-Version, and Draft-Discuss. Draft-Created states the date the document was created, Draft-Modified states when it was last modified. Draft-Version specifies the version of the draft, which MUST increase every time a modification is made. Draft-Discuss specifies the email address of a mailing list where the draft is being discussed. Final and Obsolete TEPs MUST NOT have these fields, which are for Drafts only.
The first element of the TEP body MUST be the title of the document. A TEP SHOULD follow the title with an Abstract, which gives a brief overview of the content of the TEP. Longer TEPs MAY, after the Abstract, have a Table of Contents. After the Abstract and Table of Contents there SHOULD be an Introduction, stating the problem the TEP seeks to solve and providing needed background information.
If a TEP is Documentary, it MUST have a section entitled "Implementation," which instructs the reader how to obtain the implementation documented.
If a TEP is Best Current Practice, it MUST have a section entitled "Reference," which points the reader to one or more reference uses of the practices.
The last three sections of a TEP are author information, citations, and appendices. A TEP MUST have an author information section titled entitled "Author's Address" or "Authors' Addresses." A TEP MAY have a citation section entitled "Citations." A citations section MUST immediately follow the author information section. A TEP MAY have appendices. Appendices MUST immediately follow the citations section, or if there is no citations section, the author information section. Appendices are lettered. Please refer to Appendix A for details.
The reference use of this document is TEP 1 (itself).
The definitions of the compliance terms are a direct copy of definitions taken from IETF RFC 2119.
| [1] | reStructuredText Markup Specification. <http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html> |
This is an example appendix. Appendices begin with the letter A.


True fail-fast async error handling with Expression
How it was said in the previous article there is no way to do truly fail-fast async error handling using only scala or scalaz.
Look at the example below:
val longFut = longFuture() // very long future
val shortFut = shortFuture()
val failedFut = failedFuture() // throw new IllegalStateException("future is failed")
val result = for {
long <- longFut
short <- shortFut
failed <- failedFut
} yield {
long + " | " + short + " | " + failed
}
In that example we will wait all the futures until we get IllegalStateException
because for-comprehension
always handle futures in the order which we define them since Scala translates the example above to this:
longFut.flatMap { long =>
shortFut.flatMap { short =>
failedFut.map { failed =>
long + " | " + short + " | " + failed
}
}
}
But it is possible to avoid this problem with Expression library(link)
Simple example
You can checkout a project with examples for this article here Let’s take a look at a simple example at first:
def future1(): Future[String] = Future.successful("future1")
def future2(): Future[String] = Future.successful("future2")
def resultCalc(str1: String, str2: String): String = str1 + " | " + str2
def expression(): Future[String] = Expression[Future, String] {
val result1 = extract(future1())
val result2 = extract(future2())
resultCalc(result1, result2)
}
In the example above we defined Expression
block, executed our async methods inside it, used extract(...)
method to get a result from a future and passed the future resuts as params to resultCalc(...)
.
expression()
method will behave almost like this code:
def forComprehension(): Future[String] = {
val fut1 = future1()
val fut2 = future2()
for {
result1 <- fut1
result2 <- fut2
} yield {
resultCalc(result1, result2)
}
}
except the case when the futures have finished with exceptions.
Async fail-fast error handling
In this case Expression
block will be completed with an error as soon as any of the futures is finished with an failure.
Take a look:
def expressionFailFast(): Unit = {
val startTime = System.currentTimeMillis()
val result = Expression[Future, String] {
//same order as in the for-comprehension example
val long = extract(longFuture()) //Thread.sleep(15000)
val short = extract(shortFuture()) //Thread.sleep(3000)
val failed = extract(failedFuture())//Thread.sleep(6000);throw new IllegalStateException()
short + " | " + long + " | " + failed
}
try {
Await.result(result, 30 seconds)
} catch {
case err: Throwable => println("error: " + err.getMessage)
}
val duration = System.currentTimeMillis() - startTime
println("duration: " + duration / 1000.0d)
}
If you run this code you will see that the code will be completed during ~6 seconds instead of 15 seconds for forComprehensionFailFast(...)
below:
def forComprehensionFailFast(): Unit = {
val startTime = System.currentTimeMillis()
val longFut = longFuture()
val shortFut = shortFuture()
val failedFut = failedFuture()
val result = for {
long <- longFut
short <- shortFut
failed <- failedFut
} yield {
long + " | " + short + " | " + failed
}
try {
Await.result(result, 30 seconds)
} catch {
case err: Throwable => println("error: " + err.getMessage)
}
val duration = System.currentTimeMillis() - startTime
println("duration: " + duration / 1000.0d)
}
So now it is not nessesary to wait long operations if any other operation already completed with an error. It might be helpful for working with external services which can be very slow.
Futures and If-Statements
One more thing. Sometimes you may need to use if-statement
blocks along with async operations. In this case Expression library can also be useful. Let’s imagine that we want to get and to check some information. After the checking we decide what we will do next. And all these operations should be asynchronous.
If we use for-comprehension
then a code will be like this:
def forComprehensionIf(): Future[String] = {
(for {
condition <- trueFuture()
} yield {
if(condition) {
for{
normal <- normalFuture()
long <- longFuture()
} yield {
resultCalc(normal, long)
}
} else {
for{
failed <- failedFuture()
} yield {
resultCalc("default", failed)
}
}
}).flatMap(identity)
}
It’s quite tricky, isn’t it?
But if we use Expression
then we can rewrite it like this:
def expressionIf(): Future[String] = {
//we can use extract(trueFuture())
//or we can import Expression.auto.extract instead
import com.github.jedesah.Expression.auto.extract
Expression[Future, String] {
if(trueFuture()) {
//A-branch
resultCalc(normalFuture(), longFuture())
} else {
//B-branch
resultCalc("default", failedFuture())
}
}
}
Much simpler :)
Git as Maven Repo
And one more trick for github. Sometimes you can find useful projects or libs which you want to use it in your sbt project as a managed dependency but they are not published on any public repositories. In this case jitpack.io might help you.
I’ve used it in the example project for this article to add jedesah/computation-expressions to build.sbt
//add jitpack.io to resolvers
resolvers += "jitpack.io" at "https://jitpack.io"
//add github project as a dependency
//https://github.com/jedesah -> "com.github.jedesah" as a group id
//project name "computation-expressions" as an artifact id
//and commit id "5ef11fc97c" as a revision
libraryDependencies += "com.github.jedesah" % "computation-expressions" % "5ef11fc97c"
That`s all. Now you can use any github projects as a sbt dependency.
Links
- Sources for examples - sbt project with examples
- jedesah/computation-expressions - Expression project
- jitpack.io - package repository for GitHub
- Building a Better Future - Jean-Remi Desjardins & Eddie Carlson talk about how they have created Expression library
- Slides - slides of a presentation. Can be useful.


goCars!
Disconnect
Connected as:
{{ .Username }}
Add Your Vehicles
Jeep
Bike
Boat
Slow But Reliable
Fast And Furious
Road Rage
Add
#
Vehicle
Count
{{ range .Vehicles.List }}
x
{{ .Name }}
{{ .Count }}
{{ end }}


Say Count
Say Count
Attend Count
Absense Count
Late Count
Sort By Json Index
Sort By Member Type
Sort By Count
powered by
Geo for Bootstrap


Galvanize Delivers
Order Delivery
Menu
Order
Royale with Cheese
$8.99
Arugula Pie
$11.99
Smoked Swine
$14.99
Ice Cream Biscut
$7.99


{{ metadata.get('title') }} {% if metadata.get('unedited', false) %} [Unedited] {% endif %}
{{ render_byline() }}Introduction
"Use a Content Delivery Network" was one of Steve Souders original recommendations for making web sites load faster. It's advice that remains valid today, and in this chapter of the Web Almanac we're going to explore how widely Steve's recommendation has been adopted, how sites are using Content Delivery Networks (CDNs), and some of the features they're using.
Fundamentally, CDNs reduce latency—the time it takes for packets to travel between two points on a network, say from a visitor's device to a server—and latency is a key factor in how quickly pages load.
A CDN reduces latency in two ways: by serving content from locations that are closer to the user and second, by terminating the TCP connection closer to the end user.
Historically, CDNs were used to cache, or copy, bytes so that the logical path from the user to the bytes becomes shorter. A file that is requested by many people can be retrieved once from the origin (your server) and then stored on a server closer to the user, thus saving transfer time.
CDNs also help with TCP latency. The latency of TCP determines how long it takes to establish a connection between a browser and a server, how long it takes to secure that connection, and ultimately how quickly content downloads. At best, network packets travel at roughly two-thirds of the speed of light, so how long that round trip takes depends on how far apart the two ends of the conversation are, and what's in between. Congested networks, overburdened equipment, and the type of network will all add further delays. Using a CDN to move the server end of the connection closer to the visitor reduces this latency penalty, shortening connection times, TLS negotiation times, and improving content download speeds.
Although CDNs are often thought of as just caches that store and serve static content close to the visitor, they are capable of so much more! CDNs aren't limited to just helping overcome the latency penalty, and increasingly they offer other features that help improve performance and security.
- Using a CDN to proxy dynamic content (base HTML page, API responses, etc.) can take advantage of both the reduced latency between the browser and the CDN's own network back to the origin.
- Some CDNs offer transformations that optimize pages so they download and render more quickly, or optimize images so they're the appropriate size (both dimensions and file size) for the device on which they're going to be viewed.
- From a security perspective, malicious traffic and bots can be filtered out by a CDN before the requests even reach the origin, and their wide customer base means CDNs can often see and react to new threats sooner.
- The rise of edge computing allows sites to run their own code close to their visitors, both improving performance and reducing the load on the origin.
Finally, CDNs also help sites to adopt new technologies without requiring changes at the origin, for example HTTP/2, TLS 1.3, and/or IPv6 can be enabled from the edge to the browser, even if the origin servers don't support it yet.
Caveats and disclaimers
As with any observational study, there are limits to the scope and impact that can be measured. The statistics gathered on CDN usage for the the Web Almanac does not imply performance nor effectiveness of a specific CDN vendor.
There are many limits to the testing methodology used for the Web Almanac. These include:
- Simulated network latency: The Web Almanac uses a dedicated network connection that synthetically shapes traffic.
- Single geographic location: Tests are run from a single datacenter and cannot test the geographic distribution of many CDN vendors.
- Cache effectiveness: Each CDN uses proprietary technology and many, for security reasons, do not expose cache performance.
- Localization and internationalization: Just like geographic distribution, the effects of language and geo-specific domains are also opaque to the testing.
- CDN detection is primarily done through DNS resolution and HTTP headers. Most CDNs use a DNS CNAME to map a user to an optimal datacenter. However, some CDNs use AnyCast IPs or direct A+AAAA responses from a delegated domain which hide the DNS chain. In other cases, websites use multiple CDNs to balance between vendors which is hidden from the single-request pass of WebPageTest. All of this limits the effectiveness in the measurements.
Most importantly, these results reflect a potential utilization but do not reflect actual impact. YouTube is more popular than "ShoesByColin" yet both will appear as equal value when comparing utilization.
With this in mind, there are a few intentional statistics that were not measured with the context of a CDN:
- TTFB: Measuring the Time to first byte by CDN would be intellectually dishonest without proper knowledge about cacheability and cache effectiveness. If one site uses a CDN for round-trip-time (RTT) management but not for caching, this would create a disadvantage when comparing another site that uses a different CDN vendor but does also caches the content. (Note: this does not apply to the TTFB analysis in the Performance chapter because it does not draw conclusions about the performance of individual CDNs.)
- Cache Hit vs. Cache Miss performance: As mentioned previously, this is opaque to the testing apparatus and therefore repeat tests to test page performance with a cold cache vs. a hot cache are unreliable.
Further stats
In future versions of the Web Almanac, we would expect to look more closely at the TLS and RTT management between CDN vendors. Of interest would the impact of OCSP stapling, differences in TLS Cipher performance. CWND (TCP congestion window) growth rate, and specifically the adoption of BBR v1, v2, and traditional TCP Cubic.
CDN adoption and usage
For websites, a CDN can improve performance for the primary domain (www.shoesbycolin.com
), sub-domains or sibling domains (images.shoesbycolin.com
or checkout.shoesbycolin.com
), and finally third parties (Google Analytics, etc.). Using a CDN for each of these use cases improves performance in different ways.
Historically, CDNs were used exclusively for static resources like CSS, JavaScript, and images. These resources would likely be versioned (include a unique number in the path) and cached long-term. In this way we should expect to see higher adoption of CDNs on sub-domains or sibling domains compared to the base HTML domains. The traditional design pattern would expect that www.shoesbycolin.com
would serve HTML directly from a datacenter (or ORIGIN) while static.shoesbycolin.com
would use a CDN.
Indeed, this traditional pattern is what we observe on the majority of websites crawled. The majority of web pages (80%) serve the base HTML from ORIGIN. This breakdown is nearly identical between mobile and desktop with only 0.4% lower usage of CDNs on desktop. This slight variance is likely due to the small continued use of mobile specific web pages ("mDot"), which more frequently use a CDN.
Likewise, resources served from sub-domains are more likely to utilize a CDN at 40% of sub-domain resources. Sub-domains are used either to partition resources like images and CSS or they are used to reflect organizational teams such as checkout or APIs.
Despite first-party resources still largely being served directly from ORIGIN, third-party resources have a substantially higher adoption of CDNs. Nearly 66% of all third-party resources are served from a CDN. Since third-party domains are more likely a SaaS integration, the use of CDNs are more likely core to these business offerings. Most third-party content breaks down to shared resources (JavaScript or font CDNs), augmented content (advertisements), or statistics. In all these cases, using a CDN will improve the performance and offload for these SaaS solutions.
Top CDN providers
There are two categories of CDN providers: the generic and the purpose-fit CDN. The generic CDN providers offer customization and flexibility to serve all kinds of content for many industries. In contrast, the purpose-fit CDN provider offers similar content distribution capabilities but are narrowly focused on a specific solution.
This is clearly represented when looking at the top CDNs found serving the base HTML content. The most frequent CDNs serving HTML are generic CDNs (Cloudflare, Akamai, Fastly) and cloud solution providers who offer a bundled CDN (Google, Amazon) as part of the platform service offerings. In contrast, there are only a few purpose-fit CDN providers, such as Wordpress and Netlify, that deliver base HTML markup.
| HTML CDN Usage (%) | |
|---|---|
| ORIGIN | 80.39 |
| Cloudflare | 9.61 |
| 5.54 | |
| Amazon CloudFront | 1.08 |
| Akamai | 1.05 |
| Fastly | 0.79 |
| WordPress | 0.37 |
| Sucuri Firewall | 0.31 |
| Incapsula | 0.28 |
| Myra Security CDN | 0.1 |
| OVH CDN | 0.08 |
| Netlify | 0.06 |
| Edgecast | 0.04 |
| GoCache | 0.03 |
| Highwinds | 0.03 |
| CDNetworks | 0.02 |
| Limelight | 0.01 |
| Level 3 | 0.01 |
| NetDNA | 0.01 |
| StackPath | 0.01 |
| Instart Logic | 0.01 |
| Azion | 0.01 |
| Yunjiasu | 0.01 |
| section.io | 0.01 |
| Microsoft Azure | 0.01 |
Sub-domain requests have a very similar composition. Since many websites use sub-domains for static content, we see a shift to a higher CDN usage. Like the base page requests, the resources served from these sub-domains utilize generic CDN offerings.
| Sub-Domain CDN Usage (%) | |
|---|---|
| ORIGIN | 60.56 |
| Cloudflare | 10.06 |
| 8.86 | |
| Amazon CloudFront | 6.24 |
| Akamai | 3.5 |
| Edgecast | 1.97 |
| WordPress | 1.69 |
| Highwinds | 1.24 |
| Limelight | 1.18 |
| Fastly | 0.8 |
| CDN77 | 0.43 |
| KeyCDN | 0.41 |
| NetDNA | 0.37 |
| CDNetworks | 0.36 |
| Incapsula | 0.29 |
| Microsoft Azure | 0.28 |
| Reflected Networks | 0.28 |
| Sucuri Firewall | 0.16 |
| BunnyCDN | 0.13 |
| OVH CDN | 0.12 |
| Advanced Hosters CDN | 0.1 |
| Myra Security CDN | 0.07 |
| CDNvideo | 0.07 |
| Level 3 | 0.06 |
| StackPath | 0.06 |
The composition of top CDN providers dramatically shifts for third-party resources. Not only are CDNs more frequently observed hosting third-party resources, there is also an increase in purpose-fit CDN providers such as Facebook, Twitter, and Google.
| Third-Party CDN Usage (%) | |
|---|---|
| ORIGIN | 34.27 |
| 29.61 | |
| 8.47 | |
| Akamai | 5.25 |
| Fastly | 5.14 |
| Cloudflare | 4.21 |
| Amazon CloudFront | 3.87 |
| WordPress | 2.06 |
| Edgecast | 1.45 |
| 1.27 | |
| Highwinds | 0.94 |
| NetDNA | 0.77 |
| Cedexis | 0.3 |
| CDNetworks | 0.22 |
| section.io | 0.22 |
| jsDelivr | 0.2 |
| Microsoft Azure | 0.18 |
| Yahoo | 0.18 |
| BunnyCDN | 0.17 |
| CDNvideo | 0.16 |
| Reapleaf | 0.15 |
| CDN77 | 0.14 |
| KeyCDN | 0.13 |
| Azion | 0.09 |
| StackPath | 0.09 |
RTT and TLS management
CDNs can offer more than simple caching for website performance. Many CDNs also support a pass-through mode for dynamic or personalized content when an organization has a legal or other business requirement prohibiting the content from being cached. Utilizing a CDN's physical distribution enables increased performance for TCP RTT for end users. As others have noted, reducing RTT is the most effective means to improve web page performance compared to increasing bandwidth.
Using a CDN in this way can improve page performance in two ways:
-
Reduce RTT for TCP and TLS negotiation. The speed of light is only so fast and CDNs offer a highly distributed set of data centers that are closer to the end users. In this way the logical (and physical) distance that packets must traverse to negotiate a TCP connection and perform the TLS handshake can be greatly reduced.
Reducing RTT has three immediate benefits. First, it improves the time for the user to receive data, because TCP+TLS connection time are RTT-bound. Secondly, this will improve the time it takes to grow the congestion window and utilize the full amount of bandwidth the user has available. Finally, it reduces the probability of packet loss. When the RTT is high, network interfaces will time-out requests and resend packets. This can result in double packets being delivered.
CDNs can utilize pre-warmed TCP connections to the back-end origin. Just as terminating the connection closer to the user will improve the time it takes to grow the congestion window, the CDN can relay the request to the origin on pre-established TCP connections that have already maximized congestion windows. In this way the origin can return the dynamic content in fewer TCP round trips and the content can be more effectively ready to be delivered to the waiting user.
TLS negotiation time: origin 3x slower than CDNs
Since TLS negotiations require multiple TCP round trips before data can be sent from a server, simply improving the RTT can significantly improve the page performance. For example, looking at the base HTML page, the median TLS negotiation time for origin requests is 207 ms (for desktop WebPageTest). This alone accounts for 10% of a 2 second performance budget, and this is under ideal network conditions where there is no latency applied on the request.
In contrast, the median TLS negotiation for the majority of CDN providers is between 60 and 70 ms. Origin requests for HTML pages take almost 3x longer to complete TLS negotiation than those web pages that use a CDN. Even at the 90th percentile, this disparity perpetuates with origin TLS negotiation rates of 427 ms compared to most CDNs which complete under 140 ms!
| p10 | p25 | p50 | p75 | p90 | |
|---|---|---|---|---|---|
| Highwinds | 58 | 58 | 60 | 66 | 94 |
| Fastly | 56 | 59 | 63 | 69 | 75 |
| WordPress | 58 | 62 | 76 | 77 | 80 |
| Sucuri Firewall | 63 | 66 | 77 | 80 | 86 |
| Amazon CloudFront | 59 | 61 | 62 | 83 | 128 |
| Cloudflare | 62 | 68 | 80 | 92 | 103 |
| Akamai | 57 | 59 | 72 | 93 | 134 |
| Microsoft Azure | 62 | 93 | 97 | 98 | 101 |
| Edgecast | 94 | 97 | 100 | 110 | 221 |
| 47 | 53 | 79 | 119 | 184 | |
| OVH CDN | 114 | 115 | 118 | 120 | 122 |
| section.io | 105 | 108 | 112 | 120 | 210 |
| Incapsula | 96 | 100 | 111 | 139 | 243 |
| Netlify | 53 | 64 | 73 | 145 | 166 |
| Myra Security CDN | 95 | 106 | 118 | 226 | 365 |
| GoCache | 217 | 219 | 223 | 234 | 260 |
| ORIGIN | 100 | 138 | 207 | 342 | 427 |
| CDNetworks | 85 | 143 | 229 | 369 | 452 |
For resource requests (including same-domain and third-party), the TLS negotiation time takes longer and the variance increases. This is expected because of network saturation and network congestion. By the time that a third-party connection is established (by way of a resource hint or a resource request) the browser is busy rendering and making other parallel requests. This creates contention on the network. Despite this disadvantage, there is still a clear advantage for third-party resources that utilize a CDN over using an origin solution.
TLS handshake performance is impacted by a number of factors. These include RTT, TLS record size, and TLS certificate size. While RTT has the biggest impact on the TLS handshake, the second largest driver for TLS performance is the TLS certificate size.
During the first round trip of the TLS handshake, the server attaches its certificate. This certificate is then verified by the client before proceeding. In this certificate exchange, the server might include the certificate chain by which it can be verified. After this certificate exchange, additional keys are established to encrypt the communication. However, the length and size of the certificate can negatively impact the TLS negotiation performance, and in some cases, crash client libraries.
The certificate exchange is at the foundation of the TLS handshake and is usually handled by isolated code paths so as to minimize the attack surface for exploits. Because of its low level nature, buffers are usually not dynamically allocated, but fixed. In this way, we cannot simply assume that the client can handle an unlimited-sized certificate. For example, OpenSSL CLI tools and Safari can successfully negotiate against https://10000-sans.badssl.com
. Yet, Chrome and Firefox fail because of the size of the certificate.
While extreme sizes of certificates can cause failures, even sending moderately large certificates has a performance impact. A certificate can be valid for one or more hostnames which are are listed in the Subject-Alternative-Name
(SAN). The more SANs, the larger the certificate. It is the processing of these SANs during verification that causes performance to degrade. To be clear, performance of certificate size is not about TCP overhead, rather it is about processing performance of the client.
Technically, TCP slow start can impact this negotiation but it is very improbable. TLS record length is limited to 16 KB, which fits into a typical initial congestion window of 10. While some ISPs might employ packet splicers, and other tools fragment congestion windows to artificially throttle bandwidth, this isn't something that a website owner can change or manipulate.
Many CDNs, however, depend on shared TLS certificates and will list many customers in the SAN of a certificate. This is often necessary because of the scarcity of IPv4 addresses. Prior to the adoption of Server-Name-Indicator
(SNI) by end users, the client would connect to a server, and only after inspecting the certificate, would the client hint which hostname the user user was looking for (using the Host
header in HTTP). This results in a 1:1 association of an IP address and a certificate. If you are a CDN with many physical locations, each location may require a dedicated IP, further aggravating the exhaustion of IPv4 addresses. Therefore, the simplest and most efficient way for CDNs to offer TLS certificates for websites that still have users that don't support SNI is to offer a shared certificate.
According to Akamai, the adoption of SNI is still not 100% globally. Fortunately there has been a rapid shift in recent years. The biggest culprits are no longer Windows XP and Vista, but now Android apps, bots, and corporate applications. Even at 99% adoption, the remaining 1% of 3.5 billion users on the internet can create a very compelling motivation for website owners to require a non-SNI certificate. Put another way, a pure play website can enjoy a virtually 100% SNI adoption among standard web browsers. Yet, if the website is also used to support APIs or WebViews in apps, particularly Android apps, this distribution can drop rapidly.
Most CDNs balance the need for shared certificates and performance. Most cap the number of SANs between 100 and 150. This limit often derives from the certificate providers. For example, LetsEncrypt, DigiCert, and GoDaddy all limit SAN certificates to 100 hostnames while Comodo's limit is 2,000. This, in turn, allows some CDNs to push this limit, cresting over 800 SANs on a single certificate. There is a strong negative correlation of TLS performance and the number of SANs on a certificate.
| p10 | p25 | p50 | p75 | p90 | |
|---|---|---|---|---|---|
| section.io | 1 | 1 | 1 | 1 | 2 |
| ORIGIN | 1 | 2 | 2 | 2 | 7 |
| Amazon CloudFront | 1 | 2 | 2 | 2 | 8 |
| WordPress | 2 | 2 | 2 | 2 | 2 |
| Sucuri Firewall | 2 | 2 | 2 | 2 | 2 |
| Netlify | 1 | 2 | 2 | 2 | 3 |
| Highwinds | 1 | 2 | 2 | 2 | 2 |
| GoCache | 1 | 1 | 2 | 2 | 4 |
| 1 | 1 | 2 | 3 | 53 | |
| OVH CDN | 2 | 2 | 3 | 8 | 19 |
| Cloudflare | 1 | 1 | 3 | 39 | 59 |
| Microsoft Azure | 2 | 2 | 2 | 43 | 47 |
| Edgecast | 2 | 4 | 46 | 56 | 130 |
| Incapsula | 2 | 2 | 11 | 78 | 140 |
| Akamai | 2 | 18 | 57 | 85 | 95 |
| Fastly | 1 | 2 | 77 | 100 | 100 |
| Myra Security CDN | 2 | 2 | 18 | 139 | 145 |
| CDNetworks | 2 | 7 | 100 | 360 | 818 |
| p10 | p25 | p50 | p75 | p90 | |
|---|---|---|---|---|---|
| section.io | 1 | 1 | 1 | 1 | 1 |
| ORIGIN | 1 | 2 | 2 | 3 | 10 |
| Amazon CloudFront | 1 | 1 | 2 | 2 | 6 |
| Highwinds | 2 | 2 | 2 | 3 | 79 |
| WordPress | 2 | 2 | 2 | 2 | 2 |
| NetDNA | 2 | 2 | 2 | 2 | 2 |
| CDN77 | 2 | 2 | 2 | 2 | 10 |
| Cloudflare | 2 | 3 | 3 | 3 | 35 |
| Edgecast | 2 | 4 | 4 | 4 | 4 |
| 2 | 4 | 4 | 4 | 4 | |
| Akamai | 2 | 2 | 5 | 20 | 54 |
| 1 | 10 | 11 | 55 | 68 | |
| 13 | 13 | 13 | 13 | 13 | |
| Fastly | 2 | 4 | 16 | 98 | 128 |
| Yahoo | 6 | 6 | 79 | 79 | 79 |
| Cedexis | 2 | 2 | 98 | 98 | 98 |
| Microsoft Azure | 2 | 43 | 99 | 99 | 99 |
| jsDelivr | 2 | 116 | 116 | 116 | 116 |
| CDNetworks | 132 | 178 | 397 | 398 | 645 |
TLS adoption
In addition to using a CDN for TLS and RTT performance, CDNs are often used to ensure patching and adoption of TLS ciphers and TLS versions. In general, the adoption of TLS on the main HTML page is much higher for websites that use a CDN. Over 76% of HTML pages are served with TLS compared to the 62% from origin-hosted pages.
Each CDN offers different rates of adoption for both TLS and the relative ciphers and versions offered. Some CDNs are more aggressive and roll out these changes to all customers whereas other CDNs require website owners to opt-in to the latest changes and offer change-management to facilitate these ciphers and versions.
Along with this general adoption of TLS, CDN use also sees higher adoption of emerging TLS versions like TLS 1.3.
In general, the use of a CDN is highly correlated with a more rapid adoption of stronger ciphers and stronger TLS versions compared to origin-hosted services where there is a higher usage of very old and compromised TLS versions like TLS 1.0.
More discussion of TLS versions and ciphers can be found in the Security and HTTP/2 chapters.
HTTP/2 adoption
Along with RTT management and improving TLS performance, CDNs also enable new standards like HTTP/2 and IPv6. While most CDNs offer support for HTTP/2 and many have signaled early support of the still-under-standards-development HTTP/3, adoption still depends on website owners to enable these new features. Despite the change-management overhead, the majority of the HTML served from CDNs has HTTP/2 enabled.
CDNs have over 70% adoption of HTTP/2, compared to the nearly 27% of origin pages. Similarly, sub-domain and third-party resources on CDNs see an even higher adoption of HTTP/2 at 90% or higher while third-party resources served from origin infrastructure only has 31% adoption. The performance gains and other features of HTTP/2 are further covered in the HTTP/2 chapter.
| HTTP/0.9 | HTTP/1.0 | HTTP/1.1 | HTTP/2 | |
|---|---|---|---|---|
| WordPress | 0 | 0 | 0.38 | 100 |
| Netlify | 0 | 0 | 1.07 | 99 |
| section.io | 0 | 0 | 1.56 | 98 |
| GoCache | 0 | 0 | 7.97 | 92 |
| NetDNA | 0 | 0 | 12.03 | 88 |
| Instart Logic | 0 | 0 | 12.36 | 88 |
| Microsoft Azure | 0 | 0 | 14.06 | 86 |
| Sucuri Firewall | 0 | 0 | 15.65 | 84 |
| Fastly | 0 | 0 | 16.34 | 84 |
| Cloudflare | 0 | 0 | 16.43 | 84 |
| Highwinds | 0 | 0 | 17.34 | 83 |
| Amazon CloudFront | 0 | 0 | 18.19 | 82 |
| OVH CDN | 0 | 0 | 25.53 | 74 |
| Limelight | 0 | 0 | 33.16 | 67 |
| Edgecast | 0 | 0 | 37.04 | 63 |
| Cedexis | 0 | 0 | 43.44 | 57 |
| Akamai | 0 | 0 | 47.17 | 53 |
| Myra Security CDN | 0 | 0.06 | 50.05 | 50 |
| 0 | 0 | 52.45 | 48 | |
| Incapsula | 0 | 0.01 | 55.41 | 45 |
| Yunjiasu | 0 | 0 | 70.96 | 29 |
| ORIGIN | 0 | 0.1 | 72.81 | 27 |
| Zenedge | 0 | 0 | 87.54 | 12 |
| CDNetworks | 0 | 0 | 88.21 | 12 |
| ChinaNetCenter | 0 | 0 | 94.49 | 6 |
| cdn | HTTP/0.9 | HTTP/1.0 | HTTP/1.1 | HTTP/2 |
|---|---|---|---|---|
| jsDelivr | 0 | 0 | 0 | 100 |
| 0 | 0 | 0 | 100 | |
| 0 | 0 | 1 | 99 | |
| section.io | 0 | 0 | 2 | 98 |
| BunnyCDN | 0 | 0 | 2 | 98 |
| KeyCDN | 0 | 0 | 4 | 96 |
| Microsoft Azure | 0 | 0 | 6 | 94 |
| WordPress | 0 | 0 | 7 | 93 |
| CDN77 | 0 | 0 | 7 | 93 |
| NetDNA | 0 | 0 | 7 | 93 |
| 0 | 0 | 8 | 92 | |
| Fastly | 0 | 0 | 10 | 90 |
| Sucuri Firewall | 0 | 0 | 14 | 86 |
| Cloudflare | 0 | 0 | 16 | 84 |
| Yahoo | 0 | 0 | 17 | 83 |
| OVH CDN | 0 | 0 | 26 | 75 |
| Amazon CloudFront | 0 | 0 | 26 | 74 |
| Cedexis | 0 | 0 | 27 | 73 |
| CDNetworks | 0 | 0 | 30 | 70 |
| Edgecast | 0 | 0 | 42 | 58 |
| Highwinds | 0 | 0 | 43 | 57 |
| Akamai | 0 | 0.01 | 47 | 53 |
| Incapsula | 0 | 0 | 56 | 44 |
| CDNvideo | 0 | 0 | 68 | 31 |
| ORIGIN | 0 | 0.07 | 69 | 31 |
Controlling CDN caching behavior
Vary
A website can control many the caching behavior of browsers and CDNs with the use of different HTTP headers. The most common is the Cache-Control
header which specifically determines how long something can be cached before returning to the origin to ensure it is up-to-date.
Another useful tool is the use of the Vary
HTTP header. This header instructs both CDNs and browsers how to fragment a cache. The Vary
header allows an origin to indicate that there are multiple representations of a resource, and the CDN should cache each variation separately. The most common example is compression. Declaring a resource as Vary: Accept-Encoding
allows the CDN to cache the same content, but in different forms like uncompressed, with gzip, or Brotli. Some CDNs even do this compression on the fly so as to keep only one copy available. This Vary
header likewise also instructs the browser how to cache the content and when to request new content.
While the main use of Vary
is to coordinate Content-Encoding
, there are other important variations that websites use to signal cache fragmentation. Using Vary
also instructs SEO bots like DuckDuckGo, Google, and BingBot that alternate content would be returned under different conditions. This has been important to avoid SEO penalties for "cloaking" (sending SEO specific content in order to game the rankings).
For HTML pages, the most common use of Vary
is to signal that the content will change based on the User-Agent
. This is short-hand to indicate that the website will return different content for desktops, phones, tablets, and link-unfurling engines (like Slack, iMessage, and Whatsapp). The use of Vary: User-Agent
is also a vestige of the early mobile era, where content was split between "mDot" servers and "regular" servers in the back-end. While the adoption for responsive web has gained wide popularity, this Vary
form remains.
In a similar way, Vary: Cookie
usually indicates that content that will change based on the logged-in state of the user or other personalization.
Resources, in contrast, don't use Vary: Cookie
as much as the HTML resources. Instead these resources are more likely to adapt based on the Accept
, Origin
, or Referer
. Most media, for example, will use Vary: Accept
to indicate that an image could be a JPEG, WebP, JPEG 2000, or JPEG XR depending on the browser's offered Accept
header. In a similar way, third-party shared resources signal that an XHR API will differ depending on which website it is embedded. This way, a call to an ad server API will return different content depending on the parent website that called the API.
The Vary
header also contains evidence of CDN chains. These can be seen in Vary
headers such as Accept-Encoding, Accept-Encoding
or even Accept-Encoding, Accept-Encoding, Accept-Encoding
. Further analysis of these chains and Via
header entries might reveal interesting data, for example how many sites are proxying third-party tags.
Many of the uses of the Vary
are extraneous. With most browsers adopting double-key caching, the use of Vary: Origin
is redundant. As is Vary: Range
or Vary: Host
or Vary: *
. The wild and variable use of Vary
is demonstrable proof that the internet is weird.
Surrogate-Control
, s-maxage
, and Pre-Check
There are other HTTP headers that specifically target CDNs, or other proxy caches, such as the Surrogate-Control
, s-maxage
, pre-check
, and post-check
values in the Cache-Control
header. In general usage of these headers is low.
Surrogate-Control
allows origins to specify caching rules just for CDNs, and as CDNs are likely to strip the header before serving responses, its low visible usage isn't a surprise, in fact it's surprising that it's actually in any responses at all! (It was even seen from some CDNs that state they strip it).
Some CDNs support post-check
as a method to allow a resource to be refreshed when it goes stale, and pre-check
as a maxage
equivalent. For most CDNs, usage of pre-check
and post-check
was below 1%. Yahoo was the exception to this and about 15% of requests had pre-check=0, post-check=0
. Unfortunately this seems to be a remnant of an old Internet Explorer pattern rather than active usage. More discussion on this can be found in the Caching chapter.
The s-maxage
directive informs proxies for how long they may cache a response. Across the Web Almanac dataset, jsDelivr is the only CDN where a high level of usage was seen across multiple resources—this isn't surprising given jsDelivr's role as a public CDN for libraries. Usage across other CDNs seems to be driven by individual customers, for example third-party scripts or SaaS providers using that particular CDN.
With 40% of sites using a CDN for resources, and presuming these resources are static and cacheable, the usage of s-maxage
seems low.
Future research might explore cache lifetimes versus the age of the resources, and the usage of s-maxage
versus other validation directives such as stale-while-revalidate
.
CDNs for common libraries and content
So far, this chapter has explored the use of commercials CDNs which the site may be using to host its own content, or perhaps used by a third-party resource included on the site.
Common libraries like jQuery and Bootstrap are also available from public CDNs hosted by Google, Cloudflare, Microsoft, etc. Using content from one of the public CDNs instead of a self-hosting the content is a trade-off. Even though the content is hosted on a CDN, creating a new connection and growing the congestion window may negate the low latency of using a CDN.
Google Fonts is the most popular of the content CDNs and is used by 55% of websites. For non-font content, Google API, Cloudflare's JS CDN, and the Bootstrap's CDN are the next most popular.
As more browsers implement partitioned caches, the effectiveness of public CDNs for hosting common libraries will decrease and it will be interesting to see whether they are less popular in future iterations of this research.
Conclusion
The reduction in latency that CDNs deliver along with their ability to store content close to visitors enable sites to deliver faster experiences while reducing the load on the origin.
Steve Souders' recommendation to use a CDN remains as valid today as it was 12 years ago, yet only 20% of sites serve their HTML content via a CDN, and only 40% are using a CDN for resources, so there's plenty of opportunity for their usage to grow further.
There are some aspects of CDN adoption that aren't included in this analysis, sometimes this was due to the limitations of the dataset and how it's collected, in other cases new research questions emerged during the analysis.
As the web continues to evolve, CDN vendors innovate, and sites use new practices CDN adoption remains an area rich for further research in future editions of the Web Almanac.