
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Sat, 04 Apr 2026 12:27:23 GMT</lastBuildDate>
        <item>
            <title><![CDATA[HTTP Analytics for 6M requests per second using ClickHouse]]></title>
            <link>https://blog.cloudflare.com/http-analytics-for-6m-requests-per-second-using-clickhouse/</link>
            <pubDate>Tue, 06 Mar 2018 13:00:00 GMT</pubDate>
            <description><![CDATA[ One of our large scale data infrastructure challenges here at Cloudflare is around providing HTTP traffic analytics to our customers. HTTP Analytics is available to all our customers via two options: ]]></description>
            <content:encoded><![CDATA[ <p>One of our large scale data infrastructure challenges here at Cloudflare is around providing HTTP traffic analytics to our customers. HTTP Analytics is available to all our customers via two options:</p><ul><li><p>Analytics tab in Cloudflare dashboard</p></li><li><p>Zone Analytics API with 2 endpoints</p><ul><li><p><a href="https://api.cloudflare.com/#zone-analytics-dashboard">Dashboard endpoint</a></p></li><li><p><a href="https://api.cloudflare.com/#zone-analytics-analytics-by-co-locations">Co-locations endpoint</a> (Enterprise plan only)</p></li></ul></li></ul><p>In this blog post I'm going to talk about the exciting evolution of the Cloudflare analytics pipeline over the last year. I'll start with a description of the old pipeline and the challenges that we experienced with it. Then, I'll describe how we leveraged ClickHouse to form the basis of a new and improved pipeline. In the process, I'll share details about how we went about schema design and performance tuning for ClickHouse. Finally, I'll look forward to what the Data team is thinking of providing in the future.</p><p>Let's start with the old data pipeline.</p>
    <div>
      <h3>Old data pipeline</h3>
      <a href="#old-data-pipeline">
        
      </a>
    </div>
    <p>The previous pipeline was built in 2014. It has been mentioned previously in <a href="https://blog.cloudflare.com/scaling-out-postgresql-for-cloudflare-analytics-using-citusdb/">Scaling out PostgreSQL for CloudFlare Analytics using CitusDB</a> and <a href="https://blog.cloudflare.com/more-data-more-data/">More data, more data</a> blog posts from the Data team.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5MY5UJgdkM35pwDy4mMOxt/2c08b73e37001788547db620f00a5a92/Old-system-architecture.jpg" />
          </figure><p>It had following components:</p><ul><li><p><b>Log forwarder </b>- collected Cap'n Proto formatted logs from the edge, notably DNS and Nginx logs, and shipped them to Kafka in Cloudflare central datacenter.</p></li><li><p><b>Kafka cluster </b>- consisted of 106 brokers with x3 replication factor, 106 partitions, ingested Cap'n Proto formatted logs at average rate 6M logs per second.</p></li><li><p><b>Kafka consumers</b> - each of 106 partitions had dedicated Go consumer (a.k.a. Zoneagg consumer), which read logs and produced aggregates per partition per zone per minute and then wrote them into Postgres.
<b>Postgres database</b> - single instance PostgreSQL database (a.k.a. RollupDB), accepted aggregates from Zoneagg consumers and wrote them into temporary tables per partition per minute. It then rolled-up the aggregates into further aggregates with aggregation cron. More specifically:</p><ul><li><p>Aggregates per partition, minute, zone → aggregates data per minute, zone</p></li><li><p>Aggregates per minute, zone → aggregates data per hour, zone</p></li><li><p>Aggregates per hour, zone → aggregates data per day, zone</p></li><li><p>Aggregates per day, zone → aggregates data per month, zone</p></li></ul></li><li><p><b>Citus Cluster</b> - consisted of Citus main and 11 Citus workers with x2 replication factor (a.k.a. Zoneagg Citus cluster), the storage behind Zone Analytics API and our BI internal tools. It had replication cron, which did remote copy of tables from Postgres instance into Citus worker shards.</p></li><li><p><b>Zone Analytics API</b> - served queries from internal PHP API. It consisted of 5 API instances written in Go and queried Citus cluster, and was not visible to external users.</p></li><li><p><b>PHP API </b>- 3 instances of proxying API, which forwarded public API queries to internal Zone Analytics API, and had some business logic on zone plans, error messages, etc.</p></li><li><p><b>Load Balancer </b>- nginx proxy, forwarded queries to PHP API/Zone Analytics API.</p></li></ul><p>Cloudflare has grown tremendously since this pipeline was originally designed in 2014. It started off processing under 1M requests per second and grew to current levels of 6M requests per second. The pipeline had served us and our customers well over the years, but began to split at the seams. Any system should be re-engineered after some time, when requirements change.</p><p>Some specific disadvantages of the original pipeline were:</p><ul><li><p><b>Postgres SPOF</b> - single PostgreSQL instance was a SPOF (Single Point of Failure), as it didn't have replicas or backups and if we were to lose this node, whole analytics pipeline could be paralyzed and produce no new aggregates for Zone Analytics API.</p></li><li><p><b>Citus main SPOF</b> - Citus main was the entrypoint to all Zone Analytics API queries and if it went down, all our customers' Analytics API queries would return errors.</p></li><li><p><b>Complex codebase</b> - thousands of lines of bash and SQL for aggregations, and thousands of lines of Go for API and Kafka consumers made the pipeline difficult to maintain and debug.</p></li><li><p><b>Many dependencies</b> - the pipeline consisted of many components, and failure in any individual component could result in halting the entire pipeline.</p></li><li><p><b>High maintenance cost</b> - due to its complex architecture and codebase, there were frequent incidents, which sometimes took engineers from the Data team and other teams many hours to mitigate.</p></li></ul><p>Over time, as our request volume grew, the challenges of operating this pipeline became more apparent, and we realized that this system was being pushed to its limits. This realization inspired us to think about which components would be ideal candidates for replacement, and led us to build new data pipeline.</p><p>Our first design of an improved analytics pipeline centred around the use of the <a href="https://flink.apache.org/">Apache Flink</a> stream processing system. We had previously used Flink for other data pipelines, so it was a natural choice for us. However, these pipelines had been at a much lower rate than the 6M requests per second we needed to process for HTTP Analytics, and we struggled to get Flink to scale to this volume - it just couldn't keep up with ingestion rate per partition on all 6M HTTP requests per second.</p><p>Our colleagues on our DNS team had already built and productionized DNS analytics pipeline atop ClickHouse. They wrote about it in <a href="https://blog.cloudflare.com/how-cloudflare-analyzes-1m-dns-queries-per-second/">"How Cloudflare analyzes 1M DNS queries per second"</a> blog post. So, we decided to take a deeper look at ClickHouse.</p>
    <div>
      <h3>ClickHouse</h3>
      <a href="#clickhouse">
        
      </a>
    </div>
    <blockquote><p>"ClickHouse не тормозит."
Translation from Russian: ClickHouse doesn't have brakes (or isn't slow)
© ClickHouse core developers</p></blockquote><p>When exploring additional candidates for replacing some of the key infrastructure of our old pipeline, we realized that using a column oriented database might be well suited to our analytics workloads. We wanted to identify a column oriented database that was horizontally scalable and fault tolerant to help us deliver good uptime guarantees, and extremely performant and space efficient such that it could handle our scale. We quickly realized that ClickHouse could satisfy these criteria, and then some.</p><p><a href="https://clickhouse.yandex/">ClickHouse</a> is an open source column-oriented database management system capable of real time generation of analytical data reports using SQL queries. It is blazing fast, linearly scalable, hardware efficient, fault tolerant, feature rich, highly reliable, simple and handy. ClickHouse core developers provide great help on solving issues, merging and maintaining our PRs into ClickHouse. For example, engineers from Cloudflare have contributed a whole bunch of code back upstream:</p><ul><li><p>Aggregate function <a href="https://clickhouse.com/docs/en/sql-reference/aggregate-functions/reference/topk">topK</a> by <a href="https://github.com/vavrusa">Marek Vavruša</a></p></li><li><p>IP prefix dictionary by Marek Vavruša</p></li><li><p>SummingMergeTree engine optimizations by Marek Vavruša</p></li><li><p><a href="https://clickhouse.com/docs/en/engines/table-engines/integrations/kafka">Kafka table Engine</a> by Marek Vavruša. We're thinking to replace Kafka Go consumers with this engine when it will be stable enough and ingest from Kafka into ClickHouse directly.</p></li><li><p>Aggregate function <a href="https://clickhouse.yandex/docs/en/single/index.html#summapkey-value">sumMap</a> by <a href="https://github.com/bocharov">Alex Bocharov</a>. Without this function it would be impossible to build our new Zone Analytics API.</p></li><li><p><a href="https://github.com/yandex/ClickHouse/pull/1636">Mark cache fix</a> by Alex Bocharov</p></li><li><p><a href="https://github.com/yandex/ClickHouse/pull/1844">uniqHLL12 function fix</a> for big cardinalities by Alex Bocharov. The description of the issue and its fix should be an interesting reading.</p></li></ul>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6mN4cq6YrpiHjBfbP6vpCh/7181a8e68b4a63cd48e42e0eaf807191/ClickHouse-uniq-functions.png" />
          </figure><p>Along with filing many bug reports, we also report about every issue we face in our cluster, which we hope will help to improve ClickHouse in future.</p><p>Even though DNS analytics on ClickHouse had been a great success, we were still skeptical that we would be able to scale ClickHouse to the needs of the HTTP pipeline:</p><ul><li><p>Kafka DNS topic has on average 1.5M messages per second vs 6M messages per second for HTTP requests topic.</p></li><li><p>Kafka DNS topic average uncompressed message size is 130B vs 1630B for HTTP requests topic.</p></li><li><p>DNS query ClickHouse record consists of 40 columns vs 104 columns for HTTP request ClickHouse record.</p></li></ul><p>After unsuccessful attempts with Flink, we were skeptical of ClickHouse being able to keep up with the high ingestion rate. Luckily, early prototype showed promising performance and we decided to proceed with old pipeline replacement. The first step in replacing the old pipeline was to design a schema for the new ClickHouse tables.</p>
    <div>
      <h3>ClickHouse schema design</h3>
      <a href="#clickhouse-schema-design">
        
      </a>
    </div>
    <p>Once we identified ClickHouse as a potential candidate, we began exploring how we could port our existing Postgres/Citus schemas to make them compatible with ClickHouse.</p><p>For our <a href="https://api.cloudflare.com/#zone-analytics-dashboard">Zone Analytics API</a> we need to produce many different aggregations for each zone (domain) and time period (minutely / hourly / daily / monthly). For deeper dive about specifics of aggregates please follow Zone Analytics API documentation or this handy <a href="https://docs.google.com/spreadsheets/d/1zQ3yI3HB2p8hiM-Jwvq1-MaeEyIouix2I-iUAPZtJYw/edit#gid=1788221216">spreadsheet</a>.</p><p>These aggregations should be available for any time range for the last 365 days. While ClickHouse is a really great tool to work with non-aggregated data, with our volume of 6M requests per second we just cannot afford yet to store non-aggregated data for that long.</p><p>To give you an idea of how much data is that, here is some "napkin-math" capacity planning. I'm going to use an average insertion rate of 6M requests per second and $100 as a cost estimate of 1 TiB to calculate storage cost for 1 year in different message formats:</p><table><tr><th><p><b>Metric</b></p></th><th><p><b>Cap'n Proto</b></p></th><th><p><b>Cap'n Proto (zstd)</b></p></th><th><p><b>ClickHouse</b></p></th></tr><tr><td><p>Avg message/record size</p></td><td><p>1630 B</p></td><td><p>360 B</p></td><td><p>36.74 B</p></td></tr><tr><td><p>Storage requirements for 1 year</p></td><td><p>273.93 PiB</p></td><td><p>60.5 PiB</p></td><td><p>18.52 PiB (RF x3)</p></td></tr><tr><td><p>Storage cost for 1 year</p></td><td><p>$28M</p></td><td><p>$6.2M</p></td><td><p>$1.9M</p></td></tr></table><p>And that is if we assume that requests per second will stay the same, but in fact it's growing fast all the time.</p><p>Even though storage requirements are quite scary, we're still considering to store raw (non-aggregated) requests logs in ClickHouse for 1 month+. See "Future of Data APIs" section below.</p>
    <div>
      <h4>Non-aggregated requests table</h4>
      <a href="#non-aggregated-requests-table">
        
      </a>
    </div>
    <p>We store over <a href="https://docs.google.com/spreadsheets/d/1zQ3yI3HB2p8hiM-Jwvq1-MaeEyIouix2I-iUAPZtJYw/edit?usp=sharing">100+ columns</a>, collecting lots of different kinds of metrics about each request passed through Cloudflare. Some of these columns are also available in our <a href="https://support.cloudflare.com/hc/en-us/articles/216672448-Enterprise-Log-Share-Logpull-REST-API">Enterprise Log Share</a> product, however ClickHouse non-aggregated requests table has more fields.</p><p>With so many columns to store and huge storage requirements we've decided to proceed with the aggregated-data approach, which worked well for us before in old pipeline and which will provide us with backward compatibility.</p>
    <div>
      <h4>Aggregations schema design #1</h4>
      <a href="#aggregations-schema-design-1">
        
      </a>
    </div>
    <p>According to the <a href="https://api.cloudflare.com/#zone-analytics-dashboard">API documentation</a>, we need to provide lots of different requests breakdowns and to satisfy these requirements we decided to test the following approach:</p><ol><li><p>Create Cickhouse <a href="https://clickhouse.com/docs/en/sql-reference/statements/create/view">materialized views</a> with <a href="https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/aggregatingmergetree">ReplicatedAggregatingMergeTree</a> engine pointing to non-aggregated requests table and containing minutely aggregates data for each of the breakdowns:</p><ul><li><p><b>Requests totals</b> - containing numbers like total requests, bytes, threats, uniques, etc.</p></li><li><p><b>Requests by colo</b> - containing requests, bytes, etc. breakdown by edgeColoId - each of 120+ Cloudflare datacenters</p></li><li><p><b>Requests by http status</b> - containing breakdown by HTTP status code, e.g. 200, 404, 500, etc.</p></li><li><p><b>Requests by content type</b> - containing breakdown by response content type, e.g. HTML, JS, CSS, etc.</p></li><li><p><b>Requests by country</b> - containing breakdown by client country (based on IP)</p></li><li><p><b>Requests by threat type</b> - containing breakdown by threat type</p></li><li><p><b>Requests by browser</b> - containing breakdown by browser family, extracted from user agent</p></li><li><p><b>Requests by ip class</b> - containing breakdown by client IP class</p></li></ul></li><li><p>Write the code gathering data from all 8 materialized views, using two approaches:</p><ul><li><p>Querying all 8 materialized views at once using JOIN</p></li><li><p>Querying each one of 8 materialized views separately in parallel</p></li></ul></li><li><p>Run performance testing benchmark against common Zone Analytics API queries</p></li></ol>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3iQvOQSik5xLDR7lkG0X9H/ee505f826947f6844c3e001bb83c7e7b/Schema-design--1-1.jpg" />
          </figure><p>Schema design #1 didn't work out well. ClickHouse JOIN syntax forces to write monstrous query over 300 lines of SQL, repeating the selected columns many times because you can do only <a href="https://github.com/yandex/ClickHouse/issues/873">pairwise joins</a> in ClickHouse.</p><p>As for querying each of materialized views separately in parallel, benchmark showed prominent, but moderate results - query throughput would be a little bit better than using our Citus based old pipeline.</p>
    <div>
      <h4>Aggregations schema design #2</h4>
      <a href="#aggregations-schema-design-2">
        
      </a>
    </div>
    <p>In our second iteration of the schema design, we strove to keep a similar structure to our existing Citus tables. To do this, we experimented with the SummingMergeTree engine, which is described in detail by the excellent ClickHouse <a href="https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/summingmergetree">documentation</a>:</p><blockquote><p>In addition, a table can have nested data structures that are processed in a special way. If the name of a nested table ends in 'Map' and it contains at least two columns that meet the following criteria... then this nested table is interpreted as a mapping of key =&gt; (values...), and when merging its rows, the elements of two data sets are merged by 'key' with a summation of the corresponding (values...).</p></blockquote><p>We were pleased to find this feature, because the SummingMergeTree engine allowed us to significantly reduce the number of tables required as compared to our initial approach. At the same time, it allowed us to match the structure of our existing Citus tables. The reason was that the ClickHouse Nested structure ending in 'Map' was similar to the <a href="https://www.postgresql.org/docs/9.6/static/hstore.html">Postgres hstore</a> data type, which we used extensively in the old pipeline.</p><p>However, there were two existing issues with ClickHouse maps:</p><ul><li><p>SummingMergeTree does aggregation for all records with same primary key, but final aggregation across all shards should be done using some aggregate function, which didn't exist in ClickHouse.</p></li><li><p>For storing uniques (uniques visitors based on IP), we need to use AggregateFunction data type, and although SummingMergeTree allows you to create column with such data type, it will not perform aggregation on it for records with same primary keys.</p></li></ul><p>To resolve problem #1, we had to create a new aggregation function <a href="https://clickhouse.yandex/docs/en/single/index.html#summapkey-value">sumMap</a>. Luckily, ClickHouse source code is of excellent quality and its core developers are very helpful with reviewing and merging requested changes.</p><p>As for problem #2, we had to put uniques into separate materialized view, which uses the ReplicatedAggregatingMergeTree Engine and supports merge of AggregateFunction states for records with the same primary keys. We're considering adding the same functionality into SummingMergeTree, so it will simplify our schema even more.</p><p>We also created a separate materialized view for the Colo endpoint because it has much lower usage (5% for Colo endpoint queries, 95% for Zone dashboard queries), so its more dispersed primary key will not affect performance of Zone dashboard queries.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2ZmeMrcqIgAI99UJgfx1HK/80923c6f467d95a1bd8d74e838f91d99/Schema-design--2.jpg" />
          </figure><p>Once schema design was acceptable, we proceeded to performance testing.</p>
    <div>
      <h3>ClickHouse performance tuning</h3>
      <a href="#clickhouse-performance-tuning">
        
      </a>
    </div>
    <p>We explored a number of avenues for performance improvement in ClickHouse. These included tuning index granularity, and improving the merge performance of the SummingMergeTree engine.</p><p>By default ClickHouse recommends to use 8192 index granularity. There is <a href="https://medium.com/@f1yegor/clickhouse-primary-keys-2cf2a45d7324">nice article</a> explaining ClickHouse primary keys and index granularity in depth.</p><p>While default index granularity might be excellent choice for most of use cases, in our case we decided to choose the following index granularities:</p><ul><li><p>For the main non-aggregated requests table we chose an index granularity of 16384. For this table, the number of rows read in a query is typically on the order of millions to billions. In this case, a large index granularity does not make a huge difference on query performance.</p></li><li><p>For the aggregated requests_* stables, we chose an index granularity of 32. A low index granularity makes sense when we only need to scan and return a few rows. It made a huge difference in API performance - query latency decreased by 50% and throughput increased by ~3 times when we changed index granularity 8192 → 32.</p></li></ul><p>Not relevant to performance, but we also disabled the min_execution_speed setting, so queries scanning just a few rows won't return exception because of "slow speed" of scanning rows per second.</p><p>On the aggregation/merge side, we've made some ClickHouse optimizations as well, like <a href="https://github.com/yandex/ClickHouse/pull/1330">increasing SummingMergeTree maps merge speed</a> by x7 times, which we contributed back into ClickHouse for everyone's benefit.</p><p>Once we had completed the performance tuning for ClickHouse, we could bring it all together into a new data pipeline. Next, we describe the architecture for our new, ClickHouse-based data pipeline.</p>
    <div>
      <h3>New data pipeline</h3>
      <a href="#new-data-pipeline">
        
      </a>
    </div>
    <p>The new pipeline architecture re-uses some of the components from old pipeline, however it replaces its most weak components.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2UaoX6fPvFuhN1ecJpbe6v/8aee3bd1ab395cb4a4ac2b8db9575a23/New-system-architecture.jpg" />
          </figure><p>New components include:</p><ul><li><p><b>Kafka consumers </b>- 106 Go consumers per each partition consume Cap'n Proto raw logs and extract/prepare needed 100+ ClickHouse fields. Consumers no longer do any aggregation logic.</p></li><li><p><b>ClickHouse cluster</b> - 36 nodes with x3 replication factor. It handles non-aggregate requests logs ingestion and then produces aggregates using materialized views.</p></li><li><p><b>Zone Analytics API</b> - rewritten and optimized version of API in Go, with many meaningful metrics, healthchecks, failover scenarios.</p></li></ul><p>As you can see the architecture of new pipeline is much simpler and fault-tolerant. It provides Analytics for all our 7M+ customers' domains totalling more than 2.5 billion monthly unique visitors and over 1.5 trillion monthly page views.</p><p>On average we process 6M HTTP requests per second, with peaks of upto 8M requests per second.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/zjubA3CgetykRE8Angxln/7509dd5dea82ad7408339a5f78561b41/HTTP-Logfwdr-throughput.png" />
          </figure><p>Average log message size in <a href="https://capnproto.org/">Cap’n Proto</a> format used to be ~1630B, but thanks to amazing job on Kafka compression by our Platform Operations Team, it decreased significantly. Please see <a href="https://blog.cloudflare.com/squeezing-the-firehose/">"Squeezing the firehose: getting the most from Kafka compression"</a> blog post with deeper dive into those optimisations.</p>
    <div>
      <h4>Benefits of new pipeline</h4>
      <a href="#benefits-of-new-pipeline">
        
      </a>
    </div>
    <ul><li><p><b>No SPOF</b> - removed all SPOFs and bottlenecks, everything has at least x3 replication factor.</p></li><li><p><b>Fault-tolerant</b> - it's more fault-tolerant, even if Kafka consumer or ClickHouse node or Zone Analytics API instance fails, it doesn't impact the service.</p></li><li><p><b>Scalable</b> - we can add more Kafka brokers or ClickHouse nodes and scale ingestion as we grow. We are not so confident about query performance when cluster will grow to hundreds of nodes. However, Yandex team managed to scale their cluster to 500+ nodes, distributed geographically between several data centers, using two-level sharding.</p></li><li><p><b>Reduced complexity</b> - due to removing messy crons and consumers which were doing aggregations and <a href="https://www.cloudflare.com/learning/cloud/how-to-refactor-applications/">refactoring</a> API code we were able to:</p><ul><li><p>Shutdown Postgres RollupDB instance and free it up for reuse.</p></li><li><p>Shutdown Citus cluster 12 nodes and free it up for reuse. As we won't use Citus for serious workload anymore we can reduce our operational and support costs.</p></li><li><p>Delete tens of thousands of lines of old Go, SQL, Bash, and PHP code.</p></li><li><p>Remove WWW PHP API dependency and extra latency.</p></li></ul></li><li><p><b>Improved API throughput and latency </b>- with previous pipeline Zone Analytics API was struggling to serve more than 15 queries per second, so we had to introduce temporary hard rate limits for largest users. With new pipeline we were able to remove hard rate limits and now we are serving ~40 queries per second. We went further and did intensive load testing for new API and with current setup and hardware we are able serve up to ~150 queries per second and this is scalable with additional nodes.
</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1TCHFdGAndAank2N3agHQj/5db6811ef0918e8fc66b8543531f9733/Zone-Analytics-API-requests-latency-quantiles.png" />
          </figure><p></p></li><li><p><b>Easier to operate</b> - with shutdown of many unreliable components, we are finally at the point where it's relatively easy to operate this pipeline. ClickHouse quality helps us a lot in this matter.</p></li><li><p><b>Decreased amount of incidents</b> - with new more reliable pipeline, we now have fewer incidents than before, which ultimately has reduced on-call burden. Finally, we can sleep peacefully at night :-).</p></li></ul><p>Recently, we've improved the throughput and latency of the new pipeline even further with better hardware. I'll provide details about this cluster below.</p>
    <div>
      <h4>Our ClickHouse cluster</h4>
      <a href="#our-clickhouse-cluster">
        
      </a>
    </div>
    <p>In total we have 36 ClickHouse nodes. The new hardware is a big upgrade for us:</p><ul><li><p><b>Chassis</b> - Quanta D51PH-1ULH chassis instead of Quanta D51B-2U chassis (2x less physical space)</p></li><li><p><b>CPU</b> - 40 logical cores E5-2630 v3 @ 2.40 GHz instead of 32 cores E5-2630 v4 @ 2.20 GHz</p></li><li><p><b>RAM</b> - 256 GB RAM instead of 128 GB RAM</p></li><li><p><b>Disks</b> - 12 x 10 TB Seagate ST10000NM0016-1TT101 disks instead of 12 x 6 TB Toshiba TOSHIBA MG04ACA600E</p></li><li><p><b>Network</b> - 2 x 25G Mellanox ConnectX-4 in MC-LAG instead of 2 x 10G Intel 82599ES</p></li></ul><p>Our Platform Operations team noticed that ClickHouse is not great at running heterogeneous clusters yet, so we need to gradually replace all nodes in the existing cluster with new hardware, all 36 of them. The process is fairly straightforward, it's no different than replacing a failed node. The problem is that <a href="https://github.com/yandex/ClickHouse/issues/1821">ClickHouse doesn't throttle recovery</a>.</p><p>Here is more information about our cluster:</p><ul><li><p><b>Avg insertion rate</b> - all our pipelines bring together 11M rows per second.</p></li><li><p><b>Avg insertion bandwidth</b> - 47 Gbps.</p></li><li><p><b>Avg queries per second</b> - on average cluster serves ~40 queries per second with frequent peaks up to ~80 queries per second.</p></li><li><p><b>CPU time</b> - after recent hardware upgrade and all optimizations, our cluster CPU time is quite low.
</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/61sIThxxM4s9mgA8nQSibn/4e09df1a744f0c1e2cd92b8bf5bfdd5f/ClickHouse-CPU-usage.png" />
          </figure><p></p></li><li><p><b>Max disk IO</b> (device time) - it's low as well.
</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1rNrJvGvXd6VNvPRH2qe0l/f7813d61e834b420515047062e25e791/Max-disk-IO.png" />
          </figure><p></p><p></p></li></ul><p>In order to make the switch to the new pipeline as seamless as possible, we performed a transfer of historical data from the old pipeline. Next, I discuss the process of this data transfer.</p>
    <div>
      <h4>Historical data transfer</h4>
      <a href="#historical-data-transfer">
        
      </a>
    </div>
    <p>As we have 1 year storage requirements, we had to do one-time ETL (Extract Transfer Load) from the old Citus cluster into ClickHouse.</p><p>At Cloudflare we love Go and its goroutines, so it was quite straightforward to write a simple ETL job, which:</p><ul><li><p>For each minute/hour/day/month extracts data from Citus cluster</p></li><li><p>Transforms Citus data into ClickHouse format and applies needed business logic</p></li><li><p>Loads data into ClickHouse</p></li></ul><p>The whole process took couple of days and over 60+ billions rows of data were transferred successfully with consistency checks. The completion of this process finally led to the shutdown of old pipeline. However, our work does not end there, and we are constantly looking to the future. In the next section, I'll share some details about what we are planning.</p>
    <div>
      <h3>Future of Data APIs</h3>
      <a href="#future-of-data-apis">
        
      </a>
    </div>
    
    <div>
      <h4>Log Push</h4>
      <a href="#log-push">
        
      </a>
    </div>
    <p>We're currently working on something called "Log Push". Log push allows you to specify a desired data endpoint and have your HTTP request logs sent there automatically at regular intervals. At the moment, it's in private beta and going to support sending logs to:</p><ul><li><p>Amazon S3 bucket</p></li><li><p>Google Cloud Service bucket</p></li><li><p>Other storage services and platforms</p></li></ul><p>It's expected to be generally available soon, but if you are interested in this new product and you want to try it out please contact our Customer Support team.</p>
    <div>
      <h4>Logs SQL API</h4>
      <a href="#logs-sql-api">
        
      </a>
    </div>
    <p>We're also evaluating possibility of building new product called Logs SQL API. The idea is to provide customers access to their logs via flexible API which supports standard SQL syntax and JSON/CSV/TSV/XML format response.</p><p>Queries can extract:</p><ul><li><p><b>Raw requests logs fields</b> (e.g. SELECT field1, field2, ... FROM requests WHERE ...)</p></li><li><p><b>Aggregated data from requests logs</b> (e.g. SELECT clientIPv4, count() FROM requests GROUP BY clientIPv4 ORDER BY count() DESC LIMIT 10)</p></li></ul><p>Google BigQuery provides similar <a href="https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query">SQL API</a> and Amazon has product callled <a href="https://docs.aws.amazon.com/kinesisanalytics/latest/sqlref/analytics-sql-reference.html">Kinesis Data analytics</a> with SQL API support as well.</p><p>Another option we're exploring is to provide syntax similar to <a href="https://api.cloudflare.com/#dns-analytics-properties">DNS Analytics API</a> with filters and dimensions.</p><p>We're excited to hear your feedback and know more about your analytics use case. It can help us a lot to build new products!</p>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>All this could not be possible without hard work across multiple teams! First of all thanks to other Data team engineers for their tremendous efforts to make this all happen. Platform Operations Team made significant contributions to this project, especially Ivan Babrou and Daniel Dao. Contributions from Marek Vavruša in DNS Team were also very helpful.</p><p>Finally, Data team at Cloudflare is a small team, so if you're interested in building and operating distributed services, you stand to have some great problems to work on. Check out the <a href="https://boards.greenhouse.io/cloudflare/jobs/613800">Distributed Systems Engineer - Data</a> and <a href="https://boards.greenhouse.io/cloudflare/jobs/688056">Data Infrastructure Engineer</a> roles in London, UK and San Francisco, US, and let us know what you think.</p> ]]></content:encoded>
            <category><![CDATA[Analytics]]></category>
            <category><![CDATA[Data]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Kafka]]></category>
            <category><![CDATA[Cap'n Proto]]></category>
            <category><![CDATA[API]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[Load Balancing]]></category>
            <category><![CDATA[NGINX]]></category>
            <guid isPermaLink="false">6VEE3i8wXN2CDKWJJ16uXS</guid>
            <dc:creator>Alex Bocharov</dc:creator>
        </item>
        <item>
            <title><![CDATA[The end of the road for Server: cloudflare-nginx]]></title>
            <link>https://blog.cloudflare.com/end-of-the-road-for-cloudflare-nginx/</link>
            <pubDate>Mon, 11 Dec 2017 14:00:00 GMT</pubDate>
            <description><![CDATA[ Six years ago when I joined Cloudflare the company had a capital F, about 20 employees, and a software stack that was mostly NGINX, PHP and PowerDNS (there was even a little Apache).  ]]></description>
            <content:encoded><![CDATA[ <p>Six years ago when I joined Cloudflare the company had a capital F, about 20 employees, and a software stack that was mostly NGINX, PHP and PowerDNS (there was even a little Apache). Today, things are quite different.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1UEWCugiE60sJmoWgMYpBv/b17ace62d5ae01b8ddc78b8c4f0d5aa9/3733477729_fa27bde743_b.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA 2.0</a> <a href="https://www.flickr.com/photos/zoramite/3733477729/in/photolist-6FV3RF-CqdQpX-4roTxQ-qzv62p-9uUbhA-rE2Mda-2fBsw1-k1XGt-9NHJGZ-jXtiMZ-bLV5QH-oMQho4-2xxq76-dYNEZq-kzq28Y-jqrziP-qmeRsG-boVvyx-eubDfn-dML8WR-7GhV1v-qNf6aL-avp1Tz-eeRo4Z-qNJH1n-b2BN1r-T8fJ2a-dLe1uE-dPmYEQ-gDk2pQ-89woTE-nakE9N-6E6snR-hw8v4e-hdq5No-Yzai76-5URDVP-9rTNQD-agYaM2-d7TVdd-ayKcf5-6noHQy-eg545m-9Wmvpo-iKLTMQ-kymTqW-WMcZJd-dFuhFJ-6E3wLd-gWHXxH">image</a> by <a href="https://www.flickr.com/photos/zoramite/">Randy Merrill</a></p><p>The F got lowercased, there are now more than 500 people and the software stack has changed radically. PowerDNS is gone and has been replaced with our own DNS server, <a href="/tag/rrdns/">RRDNS</a>, written in Go. The PHP code that used to handle the business logic of dealing with our customers’ HTTP requests is now Lua code, Apache is long gone and new technologies like <a href="https://www.cloudflare.com/website-optimization/railgun/">Railgun</a>, <a href="https://www.cloudflare.com/products/cloudflare-warp/">Warp</a>, <a href="https://www.cloudflare.com/argo/">Argo</a> and Tiered Cache have been added to our ‘edge’ stack.</p><p>And yet our servers still identify themselves in HTTP responses with</p>
            <pre><code>Server: cloudflare-nginx</code></pre>
            <p>Of course, NGINX is still a part of our stack, but the code that handles HTTP requests goes well beyond the capabilities of NGINX alone. It’s also not hard to imagine a time where the role of NGINX diminishes further. We currently run four instances of NGINX on each edge machine (one for SSL, one for non-SSL, one for caching and one for connections between data centers). We used to have a fifth but it’s been deprecated and are planning for the merging of the SSL and non-SSL instances.</p><p>As we have done with other bits of software (such as the <a href="http://fallabs.com/kyototycoon/">KyotoTycoon</a> distributed key-value store or PowerDNS) we’re quite likely to write our own caching or web serving code at some point. The time may come when we no longer use NGINX for caching, for example. And so, now is a good time to switch away from <code>Server: cloudflare-nginx</code>.</p><p>We like to write our own when the cost of customizing or configuring existing open source software becomes too high. For example, we switched away from PowerDNS because it was becoming too complicated to implement all the logic we need for the services we provide.</p><p>Over the next month we will be transitioning to simply:</p>
            <pre><code>Server: cloudflare</code></pre>
            <p>If you have software that looks for <code>cloudflare-nginx</code> in the <code>Server</code> header it’s time to update it.</p><p>We’ve worked closely with companies that rely on the <code>Server</code> header to determine whether a website, application or API uses Cloudflare, so that their software or service is updated and we’ll be rolling out this change in stages between December 18, 2017 and January 15, 2018. Between those dates Cloudflare-powered HTTP responses may contain either <code>Server: cloudflare-nginx</code> or <code>Server: cloudflare</code>.</p> ]]></content:encoded>
            <category><![CDATA[NGINX]]></category>
            <category><![CDATA[SSL]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Programming]]></category>
            <guid isPermaLink="false">5vx7GVqTU8t5yGDpLVSbrD</guid>
            <dc:creator>John Graham-Cumming</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare Wants to Buy Your Meetup Group Pizza]]></title>
            <link>https://blog.cloudflare.com/cloudflare-wants-to-buy-your-meetup-group-pizza/</link>
            <pubDate>Fri, 10 Nov 2017 15:00:00 GMT</pubDate>
            <description><![CDATA[ If you’re a web dev / devops / etc. meetup group that also works toward building a faster, safer Internet, I want to support your awesome group by buying you pizza.  ]]></description>
            <content:encoded><![CDATA[ <p></p><p>If you’re a web dev / devops / etc. meetup group that also works toward building a faster, safer Internet, I want to support your awesome group by buying you pizza. If your group’s focus falls within one of the subject categories below and you’re willing to give us a 30 second shout out and tweet a photo of your group and <a href="https://twitter.com/Cloudflare">@Cloudflare</a>, your meetup’s pizza expense will be reimbursed.</p><p><a href="https://docs.google.com/document/d/1wvJjiS1iuzf_XYNaah20xTwNfjcBQeiqjmrHi9CEFWM/edit?usp=sharing">Get Your Pizza $ Reimbursed »</a></p>
    <div>
      <h3>Developer Relations at Cloudflare &amp; why we’re doing this</h3>
      <a href="#developer-relations-at-cloudflare-why-were-doing-this">
        
      </a>
    </div>
    <p>I’m <a href="https://twitter.com/fitchaj">Andrew Fitch</a> and I work on the Developer Relations team at <a href="https://www.cloudflare.com/">Cloudflare</a>. One of the things I like most about working in DevRel is empowering community members who are already doing great things out in the world. Whether they’re starting conferences, hosting local meetups, or writing educational content, I think it’s important to support them in their efforts and reward them for doing what they do. Community organizers are the glue that holds developers together socially. Let’s support them and make their lives easier by taking care of the pizza part of the equation.</p>
            <figure>
            <a href="https://www.cloudflare.com/apps/">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2LFJE5nWDC74sln8mrtauZ/de98413ff1b6f611461c5c4649444c60/FingerPhotography-Cloudfare-8733.jpg" />
            </a>
            </figure>
    <div>
      <h4>What’s in it for Cloudflare?</h4>
      <a href="#whats-in-it-for-cloudflare">
        
      </a>
    </div>
    <ol><li><p>We want web developers to target the <a href="https://www.cloudflare.com/apps/">apps platform</a></p></li><li><p>We want more people to think about <a href="https://www.cloudflare.com/careers/">working at Cloudflare</a></p></li><li><p>Some people only know of <a href="https://www.cloudflare.com/">Cloudflare</a> as a CDN, but it’s actually a security company and does much more than that. General awareness helps tell the story of what Cloudflare is about.</p></li></ol>
    <div>
      <h4>What kinds of groups we most want to support</h4>
      <a href="#what-kinds-of-groups-we-most-want-to-support">
        
      </a>
    </div>
    <p>We want to work with groups that are closely aligned with Cloudflare, groups which focus on web development, web security, devops, or tech ops. We often work with language-specific meetups (such as Go London User Group) or diversity &amp; inclusion meetups (such as Women Who Code Portland). We’d also love to work with college student groups and any other coding groups which are focused on sharing technical knowledge with the community.</p><p>To get sponsored pizza, groups must be focused on...</p><ul><li><p>Web performance &amp; optimization</p></li><li><p>Front-end frameworks</p></li><li><p>Language-specific (JavaScript / PHP / Go / Lua / etc.)</p></li><li><p>Diversity &amp; inclusion meetups for web devs / devops / tech ops</p></li><li><p>Workshops / classes for web devs / devops / tech ops</p></li></ul>
    <div>
      <h4>How it works</h4>
      <a href="#how-it-works">
        
      </a>
    </div>
    <ol><li><p>Interested groups need to read our <a href="https://docs.google.com/document/d/1wvJjiS1iuzf_XYNaah20xTwNfjcBQeiqjmrHi9CEFWM/edit">Cloudflare Meetup Group Pizza Reimbursement Rules document</a>.</p></li><li><p>If the group falls within the requirements, the group should proceed to schedule their meetup and pull the <a href="https://docs.google.com/presentation/d/1afluXpf2eYeQ7SkmV2ONrojeZwosMTNifhnZR1chNTw/edit?usp=sharing">Cloudflare Introductory Slides</a> ahead of time.</p></li><li><p>At the event, an organizer should present the Cloudflare Introductory Slides at the beginning, giving us a 30 second shout out, and take a photo of the group in front of the slides. Someone from the group should Tweet the photo and @Cloudflare, so our Community Manager may retweet.</p></li><li><p>After the event, an organizer will need to fill out our <a href="https://docs.google.com/forms/d/e/1FAIpQLSe7iY-mjum-hfEtu8W08uf8e6H5IMDevRgCZ_YXtzaEwkLEag/viewform">reimbursement form</a> where they will upload a <a href="https://www.irs.gov/pub/irs-pdf/fw9.pdf">completed W-9</a>, their mailing address, group details, and the link to the Tweet.</p></li><li><p>Within a month, the organizer should have a check from Cloudflare (and some laptop stickers for their group, if they want) in their hands.</p></li></ol>
    <div>
      <h4>Here are some examples of groups we’ve worked with so far</h4>
      <a href="#here-are-some-examples-of-groups-weve-worked-with-so-far">
        
      </a>
    </div>
    <p><a href="https://www.meetup.com/preview/Go-London-User-Group"><b>Go London User Group (GLUG)</b></a></p>
            <figure>
            <a href="https://twitter.com/LondonGophers/status/920353032746995712">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4oWPqXK3ZLOH0BA3mbJO7M/ddc7337f74e01171d7e91655dfa35f0e/Screen-Shot-2017-11-01-at-2.32.00-PM.png" />
            </a>
            </figure><p>The Go London User Group is a London-based community for anyone interested in the Go programming language.</p><p>GLUG provides offline opportunities to:</p><ul><li><p>Discuss Go and related topics</p></li><li><p>Socialize with friendly people who are interested in Go</p></li><li><p>Find or fill Go-related jobs</p></li></ul><p>They want GLUG to be a diverse and inclusive community. As such all attendees, organizers and sponsors are required to follow the <a href="https://golang.org/conduct">community code of conduct</a>.</p><p>This group’s mission is very much in alignment with Cloudflare’s guidelines, so we sponsored the pizza for their <a href="https://www.meetup.com/preview/Go-London-User-Group/events/243800263">October Gophers</a> event.</p><p><a href="https://www.meetup.com/preview/Women-Who-Code-Portland"><b>Women Who Code Portland</b></a></p>
            <figure>
            <a href="https://twitter.com/superissarah/status/923370521684688896">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1t1S26RRHOCUC2b3YwEAMD/5aec722ff115aab5edbb7da5700bee35/Screen-Shot-2017-11-01-at-2.37.09-PM.png" />
            </a>
            </figure><p>Women Who Code’s mission statement</p><p>Women Who Code is a global nonprofit dedicated to inspiring women to excel in technology careers by creating a global, connected community of women in technology.</p><p>What they offer</p><ul><li><p>Monthly Networking Nights</p></li><li><p>Study Nights (JavaScript, React, DevOps, Design + Product, Algorithms)</p></li><li><p>Technical Workshops</p></li><li><p>Community Events (Hidden Figures screening, Women Who Strength Train, Happy Hours, etc.)</p></li><li><p>Free or discounted tickets to conferences</p></li></ul><p>Women Who Code are a great example of an inclusive technical group. Cloudflare reimbursed them for their pizza at their <a href="https://www.meetup.com/preview/Women-Who-Code-Portland/events/242929022">October JavaScript Study Night</a>.</p><p><a href="https://www.meetup.com/preview/PHX-Android"><b>GDG Phoenix / PHX Android</b></a></p>
            <figure>
            <a href="https://twitter.com/GDGPhoenix/status/924314879875465216">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2PyMqJ00eEnGypcoxjmQST/878da7d678babf0a506f8ff099449db8/Screen-Shot-2017-11-01-at-2.41.46-PM.png" />
            </a>
            </figure><p>GDG Phoenix / PHX Android group is for anyone interested in Android design and development. All skill levels are welcome. Participants are welcome to the meet up to hang out and watch or be part of the conversation at events.</p><p>This group is associated with the <a href="https://developers.google.com/groups/">Google Developer Group</a> program and coordinate activities with the national organization. Group activities are not exclusively Android related, however, they can cover any technology.</p><p>Cloudflare sponsored their October event “<a href="https://www.meetup.com/preview/PHX-Android/events/242897401">How espresso works . . . Android talk with Michael Bailey</a>”.</p><hr /><p>I hope a lot of awesome groups around the world will take advantage of our offer. <b>Enjoy the pizza!</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5kKeJF3uYWwJyAkZ67TmQg/1f948496f05932879b8165e87730e456/giphy-downsized.gif" />
            
            </figure><p><a href="https://docs.google.com/document/d/1wvJjiS1iuzf_XYNaah20xTwNfjcBQeiqjmrHi9CEFWM/edit?usp=sharing">Get Your Pizza $ Reimbursed »</a></p><p>© 2017 Cloudflare, Inc. All rights reserved. The Cloudflare logo and marks are trademarks of Cloudflare. All other company and product names may be trademarks of the respective companies with which they are associated.</p> ]]></content:encoded>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[JavaScript]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[LUA]]></category>
            <category><![CDATA[Go]]></category>
            <category><![CDATA[MeetUp]]></category>
            <category><![CDATA[Cloudflare Meetups]]></category>
            <category><![CDATA[Community]]></category>
            <guid isPermaLink="false">5luIi07exijfTyC4t91k0H</guid>
            <dc:creator>Andrew Fitch</dc:creator>
        </item>
        <item>
            <title><![CDATA[A New API Binding: cloudflare-php]]></title>
            <link>https://blog.cloudflare.com/cloudflare-php-api-binding/</link>
            <pubDate>Sat, 23 Sep 2017 00:01:35 GMT</pubDate>
            <description><![CDATA[ Back in May last year, one of my colleagues blogged about the introduction of our Python binding for the Cloudflare API and drew reference to our other bindings in Go and Node. Today we are complimenting this range by introducing a new official binding, this time in PHP. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Back in May last year, one of my colleagues blogged about the introduction of our <a href="/python-cloudflare/">Python binding for the Cloudflare API</a> and drew reference to our other bindings in <a href="https://github.com/cloudflare/cloudflare-go">Go</a> and <a href="https://github.com/cloudflare/node-cloudflare">Node</a>. Today we are complimenting this range by introducing a new official binding, this time in <a href="https://github.com/cloudflare/cloudflare-php">PHP</a>.</p><p>This binding is available via Packagist as <a href="https://packagist.org/packages/cloudflare/sdk">cloudflare/sdk</a>, you can install it using Composer simply by running <code>composer require cloudflare/sdk</code>. We have documented various use-cases in our <a href="https://support.cloudflare.com/hc/en-us/articles/115001661191">"Cloudflare PHP API Binding" KB article</a> to help you get started.</p><p>Alternatively should you wish to help contribute, or just give us a star on GitHub, feel free to browse to the <a href="https://github.com/cloudflare/cloudflare-php">cloudflare-php source code</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1cb8c0aCRK1QxtwReVtMDc/de48b0c9191df68bab840c53f5efea8b/installing-cloudflare-php.png" />
            
            </figure><p>PHP is a controversial language, and there is no doubt there are elements of bad design within the language (as is the case with many other languages). However, love it or hate it, PHP is a language of high adoption; as of September 2017 <a href="https://w3techs.com/technologies/overview/programming_language/all">W3Techs</a> report that PHP is used by 82.8% of all the websites whose server-side programming language is known. In creating this binding the question clearly wasn't on the merits of PHP, but whether we wanted to help drive improvements to the developer experience for the sizeable number of developers integrating with us whilst using PHP.</p><p>In order to help those looking to contribute or build upon this library, I write this blog post to explain some of the design decisions made in putting this together.</p>
    <div>
      <h3>Exclusively for PHP 7</h3>
      <a href="#exclusively-for-php-7">
        
      </a>
    </div>
    <p>PHP 5 initially introduced the ability for type hinting on the basis of classes and interfaces, this opened up (albeit seldom used) parametric polymorphic behaviour in PHP. Type hinting on the basis of interfaces made it easier for those developing in PHP to follow the Gang of Four's famous guidance: "Program to an 'interface', not an 'implementation'."</p><p>Type hinting has slowly developed in PHP, in PHP 7.0 the ability for Scalar Type Hinting was released after a few rounds of RFCs. Additionally PHP 7.0 introduced Return Type Declarations, allowing return values to be type hinted in a similar way to argument type hinting. In this library we extensively use Scalar Type Hinting and Return Type Declarations thereby restricting the backward compatibility that's available with PHP 5.</p><p>In order for backward compatibility to be available, these improvements to type hinting simply would not be implementable and the associated benefits would be lost. With Active Support <a href="http://php.net/supported-versions.php">no longer being offered to PHP 5.6</a> and Security Support little over a year away from disappearing for the entirety of PHP 5.x, we decided the additional coverage wasn't worth the cost.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/RJX9jaiMJuc3J7e8OBCor/2998f5d9da54264b9774aa8e18fe00b6/php-support.png" />
            
            </figure>
    <div>
      <h3>Object Composition</h3>
      <a href="#object-composition">
        
      </a>
    </div>
    <blockquote><p>What do we mean by a software architecture? To me the term architecture conveys a notion of the core elements of the system, the pieces that are difficult to change. A foundation on which the rest must be built. <a href="https://www.martinfowler.com/articles/designDead.html">Martin Fowler</a></p></blockquote><p>When getting started with this package, you'll notice there are 3 classes you'll need to instantiate:</p>
            <pre><code>$key     = new \Cloudflare\API\Auth\APIKey('user@example.com', 'apiKey');
$adapter = new Cloudflare\API\Adapter\Guzzle($key);
$user    = new \Cloudflare\API\Endpoints\User($adapter);
    
echo $user-&gt;getUserID();</code></pre>
            <p>The first class being instantiated is called <code>APIKey</code> (a few other classes for authentication are available). We then proceed to instantiate the <code>Guzzle</code> class and the <code>APIKey</code> object is then injected into the constructor of the <code>Guzzle</code> class. The <code>Auth</code> interface that the <code>APIKey</code> class implements is fairly simple:</p>
            <pre><code>namespace Cloudflare\API\Auth;

interface Auth
{
    public function getHeaders(): array;
}</code></pre>
            <p>The <code>Adapter</code> interface (which the <code>Guzzle</code> class implements) makes explicit that an object built on the <code>Auth</code> interface is expected to be injected into the constructor:</p>
            <pre><code>namespace Cloudflare\API\Adapter;

use Cloudflare\API\Auth\Auth;
use Psr\Http\Message\ResponseInterface;

interface Adapter
{
...
    public function __construct(Auth $auth, string $baseURI);
...
}</code></pre>
            <p>In doing so; we define that classes which implement the <code>Adapter</code> interface are to be composed using objects made from classes which implement the <code>Auth</code> interface.</p><p>So why am I explaining basic Dependency Injection here? It is critical to understand as the design of our API changes, the mechanisms for Authentication may vary independently of the HTTP Client or indeed API Endpoints themselves. Similarly the HTTP Client or the API Endpoints may vary independently of the other elements involved. Indeed, this package already contains three classes for the purpose of authentication (<code>APIKey</code>, <code>UserServiceKey</code> and <code>None</code>) which need to be interchangeably used. This package therefore considers the possibility for changes to different components in the API and seeks to allow these components to vary independently.</p><p>Dependency Injection is also used where the parameters for an API Endpoint become more complicated then what is permitted by simpler variables types; for example, this is done for defining the Target or Configuration when configuring a Page Rule:</p>
            <pre><code>require_once('vendor/autoload.php');

$key = new \Cloudflare\API\Auth\APIKey('mjsa@junade.com', 'apiKey');
$adapter = new Cloudflare\API\Adapter\Guzzle($key);
$zones = new \Cloudflare\API\Endpoints\Zones($adapter);

$zoneID = $zones-&gt;getZoneID("junade.com");

$pageRulesTarget = new \Cloudflare\API\Configurations\PageRulesTargets('https://junade.com/noCache/*');

$pageRulesConfig = new \Cloudflare\API\Configurations\PageRulesActions();
$pageRulesConfig-&gt;setCacheLevel('bypass');

$pageRules = new \Cloudflare\API\Endpoints\PageRules($adapter);
$pageRules-&gt;createPageRule($zoneID, $pageRulesTarget, $pageRulesConfig, true, 6);</code></pre>
            <p>The structure of this project is overall based on simple object composition; this provides a far more simple object model for the long-term and a design that provides higher flexibility. For example; should we later want to create an Endpoint class which is a <a href="https://en.wikipedia.org/wiki/Composite_pattern">composite</a> of other Endpoints, it becomes fairly trivial for us to build this by implementing the same interface as the other Endpoint classes. As more code is added, we are able to keep the design of the software relatively thinly layered.</p>
    <div>
      <h3>Testing/Mocking HTTP Requests</h3>
      <a href="#testing-mocking-http-requests">
        
      </a>
    </div>
    <p>If you're interesting in helping contribute to this repository; there are two key ways you can help:</p><ol><li><p>Building out coverage of endpoints on our API</p></li><li><p>Building out test coverage of those endpoint classes</p></li></ol><p>The PHP-FIG (PHP Framework Interop Group) put together a standard on how HTTP responses can be represented in an interface, this is described in the <a href="http://www.php-fig.org/psr/psr-7/">PSR-7 standard</a>. This response interface is utilised by our HTTP <code>Adapter</code> interface in which responses to API requests are type hinted to this interface (<code>Psr\Http\Message\ResponseInterface</code>).</p><p>By using this standard, it's easier to add further abstractions for additional HTTP clients and mock HTTP responses for unit testing. Let's assume the JSON response is stored in the <code>$response</code> variable and we want to test the <code>listIPs</code> method in the <code>IPs</code> Endpoint class:</p>
            <pre><code>public function testListIPs() {
  $stream = GuzzleHttp\Psr7\stream_for($response);
  $response = new GuzzleHttp\Psr7\Response(200, ['Content-Type' =&gt; 'application/json'], $stream);
  $mock = $this-&gt;getMockBuilder(\Cloudflare\API\Adapter\Adapter::class)-&gt;getMock();
  $mock-&gt;method('get')-&gt;willReturn($response);

  $mock-&gt;expects($this-&gt;once())
    -&gt;method('get')
    -&gt;with($this-&gt;equalTo('ips'), $this-&gt;equalTo([])
  );

   $ips = new \Cloudflare\API\Endpoints\IPs($mock);
   $ips = $ips-&gt;listIPs();
   $this-&gt;assertObjectHasAttribute("ipv4_cidrs", $ips);
   $this-&gt;assertObjectHasAttribute("ipv6_cidrs", $ips);
}</code></pre>
            <p>We are able to build a simple mock of our <code>Adapter</code> interface by using the standardised PSR-7 response format, when we do so we are able to define what parameters PHPUnit expects to be passed to this mock. With a mock <code>Adapter</code> class in place we are able to test the <code>IPs</code> Endpoint class as any if it was using a real HTTP client.</p>
    <div>
      <h3>Conclusions</h3>
      <a href="#conclusions">
        
      </a>
    </div>
    <p>Through building on modern versions of PHP, using good Object-Oriented Programming theory and allowing for effective testing we hope our PHP API binding provides a developer experience that is pleasant to build upon.</p><p>If you're interesting in helping improve the design of this codebase, I'd encourage you to take a look at the <a href="https://github.com/cloudflare/cloudflare-php">PHP API binding source code</a> on GitHub (and optionally give us a star).</p><p>If you work with Go or PHP and you're interested in helping Cloudflare turn our high-traffic customer-facing API into an ever more modern service-oriented environment; we're hiring for Web Engineers in <a href="https://boards.greenhouse.io/cloudflare/jobs/745994#.WcWKLtOGPys">San Francisco</a>, <a href="https://boards.greenhouse.io/cloudflare/jobs/682927#.WcWKPdOGPys">Austin</a> and <a href="https://boards.greenhouse.io/cloudflare/jobs/574636#.WcWKQdOGPys">London</a>.</p> ]]></content:encoded>
            <category><![CDATA[php]]></category>
            <category><![CDATA[API]]></category>
            <category><![CDATA[Programming]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Python]]></category>
            <guid isPermaLink="false">1NRqPHr4Wm8QNvqlHrAE4c</guid>
            <dc:creator>Junade Ali</dc:creator>
        </item>
        <item>
            <title><![CDATA[Using Guzzle and PHPUnit for REST API Testing]]></title>
            <link>https://blog.cloudflare.com/using-guzzle-and-phpunit-for-rest-api-testing/</link>
            <pubDate>Wed, 28 Dec 2016 14:10:46 GMT</pubDate>
            <description><![CDATA[ APIs are increasingly becoming the backbone of the modern internet - whether you're ordering food from an app on your phone or browsing a blog using a modern JavaScript framework, chances are those requests are flowing through an API.  ]]></description>
            <content:encoded><![CDATA[ <p></p><p>APIs are increasingly becoming the backbone of the modern internet - whether you're ordering food from an app on your phone or browsing a blog using a modern JavaScript framework, chances are those requests are flowing through an API. Given the need for APIs to evolve through refactoring and extension, having great automated tests allows you to develop fast without needing to slow down to run manual tests to work out what’s broken. Additionally, by having tests in place you’re able to firmly identify the requirements that your API should meet, your API tests effectively form a tangible and executable specification. API Testing offers an end-to-end mechanism of testing the behaviour of your API which has advantages in both reliability and also development productivity.</p><p>In this post I'll be demonstrating how you can test RESTful APIs in an automated fashion using PHP, by building a testing framework through creative use of two packages - Guzzle and PHPUnit. The resulting tests will be something you can run outside of your API as part of your deployment or CI (Continuous Integration) process.</p><p>Guzzle acts as a powerful HTTP client which we can use to simulate HTTP Requests against our API. Though PHPUnit acts as a Unit Test framework (based on XUnit), in this case we will be using this powerful testing framework to test the HTTP responses we get back from our APIs using Guzzle.</p>
    <div>
      <h2>Preparing our Environment</h2>
      <a href="#preparing-our-environment">
        
      </a>
    </div>
    <p>In order to pull in the required packages, we’ll be using Composer - a dependency manager for PHP. Inside our Composer project, we can simply require the dependencies we’re after:</p>
            <pre><code>$ composer require phpunit/phpunit
$ composer require guzzlehttp/guzzle
$ composer update</code></pre>
            <p>When we ran <code>composer require</code> for each of the two packages, Composer went ahead and actually downloaded the packages we want, these are stored in the <code>vendor</code> directory. Additionally when we ran <code>composer update</code>, Composer updated it’s <a href="http://www.php-fig.org/psr/psr-4/">PSR-4</a> autoload script that allows us to pull in all the dependencies we’ve required with one file include, you can find this in <code>vendor/autoload.php</code>.</p><p>With our dependencies in place, we can now configure PHPUnit to use Guzzle. In order to do this, we need to tell PHPUnit where our Composer autoload file is, but also where our tests are located. We can do this through writing a <code>phpunit.xml</code> in the root directory of our project:</p>
            <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;phpunit bootstrap="vendor/autoload.php"&gt;
    &lt;testsuites&gt;
        &lt;testsuite name="REST API Test Suite"&gt;
            &lt;directory suffix=".php"&gt;./tests/&lt;/directory&gt;
        &lt;/testsuite&gt;
    &lt;/testsuites&gt;
&lt;/phpunit&gt;</code></pre>
            <p>In the XML above, the two noteworthy elements are the opening <code>phpunit</code> tag (which defines with a bootstrap property where our Composer autoload script is), additionally we have a <code>testsuite</code> element which defines our test suites (and a child <code>directory</code> property to define where the specific tests live). From here, we can just add an empty directory called <code>tests</code> for our tests to reside in.</p><p>If we now run PHPUnit (through the command <code>./vendor/bin/phpunit</code>), we should see an output similar to the one I get below:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/76DXsZeFPl1ujtAumTaWzn/6de8b3a7caf0b5fd35e5ae6e4076e463/runningPHPUnitNoTests.png" />
            
            </figure><p>With our environment defined, we’re ready to move on to the next step. First; purely for the sake of convenience, I’ve added a shortcut to my <code>composer.json</code> file so that when I run <code>composer test</code> it will point to <code>./vendor/bin/phpunit</code>. You can do this by adding the following JSON to your <code>composer.json</code> file:</p>
            <pre><code>  "scripts": {
    "test": "./vendor/bin/phpunit"
  }</code></pre>
            
    <div>
      <h2>Writing our Tests</h2>
      <a href="#writing-our-tests">
        
      </a>
    </div>
    <p>As an example, I’ll be writing tests against an endpoint at httpbin.org. The first test I’ll write will be against the <code>/user-agent</code> endpoint, so I’ll create a file called <code>UserAgentTest.php</code>, be sure to extend the <code>PHPUnit_Framework_TestCase</code> class:</p>
            <pre><code>&lt;?php

class UserAgentTest extends PHPUnit_Framework_TestCase
{
}</code></pre>
            <p>Before each test, PHPUnit will run the <code>setUp</code> method and after the test has executed it will run the <code>tearDown</code> method in the class (if they exist). By utilising these methods we can instantiate our Guzzle HTTP client ready for each test and then return to a clean slate afterwards:</p>
            <pre><code>&lt;?php

class UserAgentTest extends PHPUnit_Framework_TestCase
{
    private $http;

    public function setUp()
    {
        $this-&gt;http = new GuzzleHttp\Client(['base_uri' =&gt; 'https://httpbin.org/']);
    }

    public function tearDown() {
        $this-&gt;http = null;
    }
}</code></pre>
            <p>Note that if you feel even more adventurous, you can utilise environment variables (through the <code>getenv</code> method) to set the <code>baseurl</code> - for this tutorial however, I’ll be keeping things simple.</p><p>With our <code>setUp</code> and <code>tearDown</code> methods in place, we can now go ahead and actually create our test methods. As I’ll start off by testing against the <code>GET</code> HTTP verb, I’ll name the first test method <code>testGet</code>. From here, we can make the request and then check the properties we get back.</p>
            <pre><code>public function testGet()
{
    $response = $this-&gt;http-&gt;request('GET', 'user-agent');

    $this-&gt;assertEquals(200, $response-&gt;getStatusCode());

    $contentType = $response-&gt;getHeaders()["Content-Type"][0];
    $this-&gt;assertEquals("application/json", $contentType);

    $userAgent = json_decode($response-&gt;getBody())-&gt;{"user-agent"};
    $this-&gt;assertRegexp('/Guzzle/', $userAgent);
}</code></pre>
            <p>In the method above, I’ve made a GET request to the user-agent endpoint. I can then check the response code I get back was indeed <code>200</code> using the first assertion. The next assertion I test against is whether the <code>Content-Type</code> header indicates the response is JSON. Finally I check that the JSON body itself actually contains the phrase “Guzzle” in the user-agent property.</p><p>We can add additional assertions as required, but we can also add additional methods for other HTTP verbs. For example, here’s a simple test to see if I get a <code>405</code> status code when I make a PUT request to the <code>/user-agent</code> endpoint:</p>
            <pre><code>public function testPut()
{
    $response = $this-&gt;http-&gt;request('PUT', 'user-agent', ['http_errors' =&gt; false]);

    $this-&gt;assertEquals(405, $response-&gt;getStatusCode());
}</code></pre>
            <p>Next time we run PHPUnit, we can see if our tests pass successfully and also get insight into some statistics surrounding the execution of these tests:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6vHnEkW9LJGUDKdYPJKFpC/93bf5e18e2478b2ab1512da9490204bd/Screen-Shot-2016-12-28-at-08.36.57.png" />
            
            </figure>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>That’s all there is to this simple approach to API testing. If you want some insight into the overall code, feel free to review the project files in the <a href="https://github.com/IcyApril/Test-the-REST">Github repository</a>.</p><p>If you find yourself using this testing set-up, be sure to review the <a href="http://docs.guzzlephp.org/en/latest/request-options.html">Guzzle Request Options</a> to learn what kind of HTTP requests you can run with Guzzle and also check out the <a href="https://phpunit.de/manual/current/en/appendixes.assertions.html">types of assertions</a> of you can run with PHPUnit.</p> ]]></content:encoded>
            <category><![CDATA[php]]></category>
            <guid isPermaLink="false">3KG09Q7frQnsa3Y3MXhmyH</guid>
            <dc:creator>Junade Ali</dc:creator>
        </item>
        <item>
            <title><![CDATA[Accelerating Node.js applications with HTTP/2 Server Push]]></title>
            <link>https://blog.cloudflare.com/accelerating-node-js-applications-with-http-2-server-push/</link>
            <pubDate>Tue, 16 Aug 2016 12:17:19 GMT</pubDate>
            <description><![CDATA[ In April, we announced support for HTTP/2 Server Push via the HTTP Link header. My coworker John has demonstrated how easy it is to add Server Push to an example PHP application.  We wanted to make it easy to improve the performance of contemporary websites built with Node.js.  ]]></description>
            <content:encoded><![CDATA[ <p>In April, we announced support for <a href="https://www.cloudflare.com/http2/server-push/">HTTP/2 Server Push</a> via the HTTP <code>Link</code> header. My coworker John has demonstrated how easy it is to <a href="/using-http-2-server-push-with-php/">add Server Push to an example PHP</a> application.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2cTvkkBKYrTnHNyFrfyrYI/05d92efb1903cd4700d8a9020e430b40/489477622_594bf9e3d9_z.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/nickyfern/489477622/in/photolist-KfGDm-4WuA7B-4WySRS-a8ijnt-4WuByk-4WuB9M-bE3e6V-665C6K-eeQRx1-b97viM-qzYJ8z-9n2aTS-8EsaQK-aqxV42-jWDkD2-jKyWKv-jzs7yy-9TKcYn-4iAQTa-ECjYJ-96MvXy-bP43v2-rEmKWR-5p3k5r-pVZhDo-os1Njy-5CEEnU-8RhgUX-7JV4jr-9bC7me-sSKqA-72Mppz-maZfyL-6KfUge-dDvw4S-ngyFgY-aPxqsP-7b24QV-5opdXV-sEA7Xj-mZohS-8GFkMr-twfh93-7ZXqtJ-dvRbXj-a8mRiU-4NCzeD-qFXKpj-4n7K7j-34D66Q">image</a> by <a href="https://www.flickr.com/photos/nickyfern/">Nicky Fernandes</a></p><p>We wanted to make it easy to improve the performance of contemporary websites built with <a href="https://nodejs.org/">Node.js</a>. we developed the <a href="https://github.com/cloudflare/netjet">netjet</a> middleware to parse the generated HTML and automatically add the <code>Link</code> headers. When used with an example Express application you can see the headers being added:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4yzfZDbCTajZyJDNnbqrUm/ab28553b5d5e878e10b93f115a0f602e/2016-08-11_13-32-45.png" />
            
            </figure><p>We use <a href="https://ghost.org/">Ghost</a> to power this blog, so if your browser supports HTTP/2 you have already benefited from Server Push without realizing it! More on that below.</p><p>In netjet, we use the <a href="https://github.com/posthtml/posthtml">PostHTML</a> project to parse the HTML with a custom plugin. Right now it is looking for images, scripts and external stylesheets. You can implement this same technique in other environments too.</p><p>Putting an HTML parser in the response stack has a downside: it will increase the page load latency (or "time to first byte"). In most cases, the added latency will be overshadowed by other parts of your application, such as database access. However, netjet includes an adjustable LRU cache keyed by <code>ETag</code> headers, allowing netjet to insert <code>Link</code> headers quickly on pages already parsed.</p><p>If you are designing a brand new application, however, you should consider storing metadata on embedded resources alongside your content, eliminating the HTML parse, and possible latency increase, entirely.</p><p>Netjet is compatible with any Node.js HTML framework that supports Express-like middleware. Getting started is as simple as adding netjet to the beginning of your middleware chain.</p>
            <pre><code>var express = require('express');
var netjet = require('netjet');
var root = '/path/to/static/folder';

express()
  .use(netjet({
    cache: {
      max: 100
    }
  }))
  .use(express.static(root))
  .listen(1337);</code></pre>
            <p>With a little more work, you can even use netjet without frameworks.</p>
            <pre><code>var http = require('http');
var netjet = require('netjet');

var port = 1337;
var hostname = 'localhost';
var preload = netjet({
  cache: {
    max: 100
  }
});

var server = http.createServer(function (req, res) {
  preload(req, res, function () {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end('&lt;!doctype html&gt;&lt;h1&gt;Hello World&lt;/h1&gt;');
  });
});

server.listen(port, hostname, function () {
  console.log('Server running at http://' + hostname + ':' + port+ '/');
});</code></pre>
            <p>See the <a href="https://www.npmjs.com/package/netjet">netjet documentation</a> for more information on the supported options.</p>
    <div>
      <h2>Seeing what’s pushed</h2>
      <a href="#seeing-whats-pushed">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2qCaQEt8gl74hBdoZVEzBS/d29a6028893b87b888cad5ba438b3a78/2016-08-02_10-49-33.png" />
            
            </figure><p>Chrome's Developer Tools makes it easy to verify that your site is using Server Push. The Network tab shows pushed assets with "Push" included as part of the initiator.</p><p>Unfortunately, Firefox's Developers Tools don't yet directly expose if the resource pushed. You can, however, check for the <code>cf-h2-pushed</code> header in the page's response headers, which contains a list of resources that CloudFlare offered browsers over Server Push.</p><p>Contributions to improve netjet or the documentation are greatly appreciated. I'm excited to hear where people are using netjet.</p>
    <div>
      <h2>Ghost and Server Push</h2>
      <a href="#ghost-and-server-push">
        
      </a>
    </div>
    <p>Ghost is one such exciting integration. With the aid of the Ghost team, I've integrated netjet, and it has been available as an opt-in beta since version 0.8.0.</p><p>If you are running a Ghost instance, you can enable Server Push by modifying the server's <code>config.js</code> file and add the <code>preloadHeaders</code> option to the <code>production</code> configuration block.</p>
            <pre><code>production: { 
  url: 'https://my-ghost-blog.com', 
  preloadHeaders: 100, 
  // ... 
}</code></pre>
            <p>Ghost has put together <a href="http://support.ghost.org/preload-headers/">a support article</a> for Ghost(Pro) customers.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>With netjet, your Node.js applications can start to use browser preloading and, when used with CloudFlare, HTTP/2 Server Push today.</p><p>At CloudFlare, we're excited to make tools to help increase the performance of websites. If you find this interesting, we are <a href="https://www.cloudflare.com/join-our-team/">hiring</a> in Austin, Texas; Champaign, Illinois; London; San Francisco; and Singapore.</p> ]]></content:encoded>
            <category><![CDATA[HTTP2]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[Server Push]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[JavaScript]]></category>
            <guid isPermaLink="false">257O1UR4TetOqSjjwVfDCV</guid>
            <dc:creator>Terin Stock</dc:creator>
        </item>
        <item>
            <title><![CDATA[HTTP/2 Server Push with multiple assets per Link header]]></title>
            <link>https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header/</link>
            <pubDate>Thu, 30 Jun 2016 12:09:46 GMT</pubDate>
            <description><![CDATA[ In April we announced that we had added experimental support for HTTP/2 Server Push to all CloudFlare web sites. We did this so that our customers could iterate on this new functionality.

 ]]></description>
            <content:encoded><![CDATA[ <p>In April we <a href="/announcing-support-for-http-2-server-push-2/">announced</a> that we had added experimental support for <a href="https://www.cloudflare.com/http2/server-push/">HTTP/2 Server Push</a> to all CloudFlare web sites. We did this so that our customers could iterate on this new functionality.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1Sxm8MH00rIckiWtjvxz71/9f4c2227696b67a4e19db037e18bb80d/1673801831_a93ecfc3c4_z.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/mryipyop/1673801831/in/photolist-3xUEYg-nMS6rx-jvSsLx-9oBV3s-8KH2YK-o54g8R-8h4bmw-eapSfS-cj4No3-nMSXLP-nMRZQJ-iNpH9k-hcr3m3-nMS6ui-5RDVrK-fAaYES-nMXwba-cNFT3N-iZDTWg-k5VzKt-jeJE8T-7bM32E-d3rznG-89jZ9e-aVLkBT-4y2kdD-qtKzjY-62Yv2h-5WcKeC-87jJdA-5Es3vn-bQ8W5a-7DhbKd-hE5oza-6NutL7-5WLwFt-hcnrny-5WAoU1-5mSoVV-8RSQ7A-gXiVcG-5Wp6pS-8GFkMr-hcoeN9-bC1zH5-ePG8BQ-hcokfv-hcpgme-hcqQ59-qUkfTa">image</a> by <a href="Mike">https://www.flickr.com/photos/mryipyop/</a></p><p>Our implementation of Server Push made use of the <a href="/announcing-support-for-http-2-server-push-2/#fnref:1">HTTP <code>Link</code></a> header as detailed in W3C <a href="https://www.w3.org/TR/preload/">Preload</a> Working Draft.</p><p>We also showed how to make Server Push work from within <a href="/using-http-2-server-push-with-php/">PHP code</a> and many people started testing and using this feature.</p><p>However, there was a serious restriction in our initial version: it was not possible to specify more than one asset per <code>Link</code> header for Server Push and many CMS and web development platforms would not allow multiple <code>Link</code> headers.</p><p>We have now addressed that problem and it is possible to request that multiple assets be pushed in a single <code>Link</code> header. This change is live and was used to push assets in this blog post to your browser if your browser supports HTTP/2.</p><p>When CloudFlare reads a <code>Link</code> header sent by an origin web server it will remove assets that it pushes from the <code>Link</code> header passed on to the web browser. That made it a little difficult to debug problems with <code>Link</code> and Server Push so we have added a header called <code>Cf-H2-Pushed</code> which contains the assets that were pushed.</p><p>For example, browsing to this <a href="/the-complete-guide-to-golang-net-http-timeouts/">recent</a> blog post results in the origin web server sending the following headers:</p>
            <pre><code>Cache-Control: public, max-age=0
Content-Encoding: gzip
Content-Length: 33640
Content-Type: text/html; charset=utf-8
Date: Wed, 29 Jun 2016 16:09:37 GMT
Expires: Wed, 29 Jun 2016 16:10:07 GMT
Link: &lt;/assets/css/screen.css?v=5fc240c512&gt;; rel=preload; as=style,&lt;//cdn.bizible.com/scripts/bizible.js&gt;; rel=preload; as=script,&lt;/content/images/2016/06/Timeouts-001.png&gt;; rel=preload; as=image,&lt;/content/images/2016/06/Timeouts-002.png&gt;; rel=preload; as=image,&lt;//platform.linkedin.com/in.js&gt;; rel=preload; as=script,&lt;https://code.jquery.com/jquery-1.11.3.min.js&gt;; rel=preload; as=script,&lt;/assets/js/jquery.fitvids.js?v=5fc240c512&gt;; rel=preload; as=script
Vary: Accept-Encoding
X-Ghost-Cache-Status:From Cache
X-Powered-By: Express </code></pre>
            <p>CloudFlare decides to HTTP/2 Server Push the assets <code>/assets/css/screen.css?v=5fc240c512</code>, <code>/content/images/2016/06/Timeouts-001.png</code>, <code>/content/images/2016/06/Timeouts-002.png</code> and <code>/assets/js/jquery.fitvids.js?v=5fc240c512</code>. As the response passes through CloudFlare those assets are removed from the <code>Link</code> header, pushed and added to the <code>Cf-H2-Pushed</code> header:</p>
            <pre><code>cache-control:public, max-age=30
cf-cache-status:EXPIRED
cf-h2-pushed:&lt;/assets/css/screen.css?v=5fc240c512&gt;,&lt;/content/images/2016/06/Timeouts-001.png&gt;,&lt;/content/images/2016/06/Timeouts-002.png&gt;,&lt;/assets/js/jquery.fitvids.js?v=5fc240c512&gt;
content-encoding:gzip
content-type:text/html; charset=utf-8
date:Wed, 29 Jun 2016 16:09:37 GMT
expires:Wed, 29 Jun 2016 16:10:07 GMT
link:&lt;//cdn.bizible.com/scripts/bizible.js&gt;; rel=preload; as=script,&lt;//platform.linkedin.com/in.js&gt;; rel=preload; as=script,&lt;https://code.jquery.com/jquery-1.11.3.min.js&gt;; rel=preload; as=script
server:cloudflare-nginx
status:200 OK
vary:Accept-Encoding
x-ghost-cache-status:From Cache
x-powered-by:Express</code></pre>
            <p>In Google Chrome Canary's Developer view the pushed assets can be clearly seen.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2jgGCv0Nr84rpBYTGKMQ5l/b27d125a7a1e3f827e15118c213e4666/Screen-Shot-2016-06-30-at-11-15-02.png" />
            
            </figure>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Please let us know how you use Server Push. We're particularly interested in experiences with pushing different types of resources (images vs. styles vs. scripts) and working out the optimal number of items to push (we currently allow up to 50 resources per page).</p> ]]></content:encoded>
            <category><![CDATA[HTTP2]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[Reliability]]></category>
            <category><![CDATA[Programming]]></category>
            <category><![CDATA[Server Push]]></category>
            <guid isPermaLink="false">4z1Btq5Q3lFHwOHSsHQG6n</guid>
            <dc:creator>John Graham-Cumming</dc:creator>
        </item>
        <item>
            <title><![CDATA[Using HTTP/2 Server Push with PHP]]></title>
            <link>https://blog.cloudflare.com/using-http-2-server-push-with-php/</link>
            <pubDate>Fri, 13 May 2016 13:11:28 GMT</pubDate>
            <description><![CDATA[ Two weeks ago CloudFlare announced that it was supporting HTTP/2 Server Push for all our customers. By simply adding a Link header to an HTTP response specifying preload CloudFlare would automatically push items to web browsers that support Server Push. ]]></description>
            <content:encoded><![CDATA[ <p>Two weeks ago CloudFlare announced that it was supporting <a href="/announcing-support-for-http-2-server-push-2/">HTTP/2 Server</a> Push for all our customers. By simply adding a <code>Link</code> header to an HTTP response specifying <code>preload</code> CloudFlare would automatically push items to web browsers that support Server Push.</p><p>To illustrate how easy this is I create a small PHP page that uses the PHP <a href="https://secure.php.net/manual/en/function.header.php"><code>header</code></a> function to insert appropriate <code>Link</code> headers to push images to the web browser via CloudFlare. The web page looks like this when loaded:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2KEen2kNGsrXXEKZZnQSni/27615c7b083c1aff54f087eb5225c1c6/Screen-Shot-2016-05-13-at-11-50-00-AM.png" />
            
            </figure><p>There are two images loaded from the same server both of which are pushed if the web browser supports Server Push. This is achieved by inserting two <code>Link</code> headers in the HTTP response. The response looks like:</p>
            <pre><code>HTTP/1.1 200 OK
Server: nginx/1.9.15
Date: Fri, 13 May 2016 10:52:13 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Link: &lt;/images/drucken.jpg&gt;; rel=preload; as=image
Link: &lt;/images/empire.jpg&gt;; rel=preload; as=image</code></pre>
            <p>At the bottom are the two <code>Link</code> headers corresponding to the two images on the page with the <code>rel=preload</code> directive as specified in W3C <a href="https://www.w3.org/TR/preload/#server-push-http-2">preload draft</a>.</p><p>The complete code can be found in this <a href="https://gist.github.com/jgrahamc/df91229381366105c7ff0f88c8c38485">gist</a> but the core of the code looks like this:</p>
            <pre><code>    &lt;?php
    function pushImage($uri) {
        header("Link: &lt;{$uri}&gt;; rel=preload; as=image", false);
        return &lt;&lt;&lt;HTML
    &lt;img src="{$uri}"&gt;
    HTML;
    }

    $image1 = pushImage("/images/drucken.jpg");
    $image2 = pushImage("/images/empire.jpg");
    ?&gt;

    &lt;html&gt;

    &lt;head&gt;&lt;title&gt;PHP Server Push&lt;/title&gt;&lt;/head&gt;
    &lt;body&gt;

    &lt;h1&gt;PHP Server Push&lt;/h1&gt;

    &lt;?php
    echo ccbysa($image1, "https://bit.ly/1Wu5bYx",
       "https://www.flickr.com/photos/hiperactivo/", "Javier Candeira");

    echo ccbynd($image2, "https://bit.ly/24PHue3",
        "https://www.flickr.com/photos/bobsfever/", "Robert McGoldrick");
    ?&gt;

    &lt;/body&gt;
    &lt;/html&gt;</code></pre>
            <p>Since you have to call the PHP <code>header</code> function before any output (such as HTML) has occurred the code makes two calls to a helper function called <code>pushImage</code> first. <code>pushImage</code> adds the appropriate <code>Link</code> header and returns the HTML needed to insert the actual image in the page.</p><p>Later the variables <code>$image1</code> and <code>$image2</code> (which contain the HTML needed to display the images) is inserted use two other helper functions (<code>ccbysa</code> and <code>ccbynd</code>) that add captions. Those two helper functions don't play any part in the Server Push, they just ensure that the HTML is placed correctly on the page with a caption.</p><p>Notice that the <code>Link</code> header is added as follows:</p>
            <pre><code>header("Link: &lt;{$uri}&gt;; rel=preload; as=image", false);</code></pre>
            <p>The <code>false</code> second parameter tells <code>header</code> to <i>not</i> override an existing <code>Link</code> header. With that option specified multiple <code>Link</code> headers can be added. Without it the last call to <code>pushImage</code> would win.</p>
    <div>
      <h2>Effect of Server Push</h2>
      <a href="#effect-of-server-push">
        
      </a>
    </div>
    <p>To understand the effect of Server Push on the example I used Google Chrome Canary and loaded the page twice (once without the <code>Link</code> headers so that no push would occur and once with).</p><p>Here's the simple waterfall over HTTP/2 with no Server Push:</p>
            <figure>
            <a href="http://staging.blog.mrk.cfdata.org/content/images/2016/05/Screen-Shot-2016-05-13-at-12-22-24-PM.png">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5oN4WtS0666At1eTeB2Xkr/aa2a06fb0da68d4e5a068a944c1c6da9/Screen-Shot-2016-05-13-at-12-22-24-PM.png" />
            </a>
            </figure><p>The page load time was 651ms. You can see the initial page HTML load in 175ms followed by the two images.</p><p>How here's the waterfall with HTTP/2 Server Push:</p>
            <figure>
            <a href="http://staging.blog.mrk.cfdata.org/content/images/2016/05/Screen-Shot-2016-05-13-at-12-20-58-PM.png">
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2thpvABMv76ref5uY2LIhX/771d0c7d56cec749b84306fbbdd48ca7/Screen-Shot-2016-05-13-at-12-20-58-PM.png" />
            </a>
            </figure><p>The page load time was 251ms (the HTML loaded in 168ms) and Chrome is showing that the two images were pushed (see the Initiator column).</p><p>It's not 100% obvious what happened there but digging into chrome://net-internals/ it's possible to see a detailed HTTP/2 timeline. I've edited out a few details of the protocol (such as the window size changes) to focus in on the requests and responses.</p><p>The <code>t</code> value gives the tick time (1ms per tick).</p>
            <pre><code>t=910212 [st=   1]    HTTP2_SESSION_SEND_HEADERS
                      --&gt; exclusive = true
                      --&gt; fin = true
                      --&gt; has_priority = true
                      --&gt; :method: GET
                          :scheme: https
                          :path: /index.php
                          pragma: no-cache
                          cache-control: no-cache
                          upgrade-insecure-requests: 1
                          accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
                          accept-encoding: gzip, deflate, sdch, br
                          accept-language: en-US,en;q=0.8
                          cookie: [52 bytes were stripped]
                      --&gt; parent_stream_id = 0
                      --&gt; priority = 0
                      --&gt; stream_id = 1

[...]

t=910404 [st= 193]    HTTP2_SESSION_RECV_HEADERS
                      --&gt; fin = false
                      --&gt; :status: 200
                          date: Fri, 13 May 2016 11:28:16 GMT
                          content-type: text/html
                          content-encoding: gzip
                      --&gt; stream_id = 1
t=910405 [st= 194]    HTTP2_SESSION_RECV_PUSH_PROMISE
                      --&gt; :method: GET
                          :path: /images/drucken.jpg
                          :scheme: https
                          accept-encoding: gzip, deflate, sdch, br
                      --&gt; id = 1
                      --&gt; promised_stream_id = 2
t=910405 [st= 194]    HTTP2_SESSION_RECV_PUSH_PROMISE
                      --&gt; :method: GET
                          :path: /images/empire.jpg
                          :scheme: https
                          accept-encoding: gzip, deflate, sdch, br
                      --&gt; id = 1
                      --&gt; promised_stream_id = 4
t=910405 [st= 194]    HTTP2_SESSION_RECV_DATA
                      --&gt; fin = false
                      --&gt; size = 298
                      --&gt; stream_id = 1
t=910405 [st= 194]    HTTP2_SESSION_RECV_DATA
                      --&gt; fin = true
                      --&gt; size = 0
                      --&gt; stream_id = 1
t=910409 [st= 198]    HTTP2_SESSION_RECV_HEADERS
                      --&gt; fin = false
                      --&gt; :status: 200
                          date: Fri, 13 May 2016 11:28:16 GMT
                          content-type: image/jpeg
                          content-length: 49852
                          set-cookie: [124 bytes were stripped]
                          etag: "5735aac0-12f99"
                          last-modified: Fri, 13 May 2016 10:21:52 GMT
                          cf-cache-status: HIT
                          vary: Accept-Encoding
                          expires: Fri, 13 May 2016 15:28:16 GMT
                          cache-control: public, max-age=14400
                          accept-ranges: bytes
                          server: cloudflare-nginx
                      --&gt; stream_id = 2
t=910409 [st= 198]    HTTP2_SESSION_RECV_DATA
                      --&gt; fin = false
                      --&gt; size = 987
                      --&gt; stream_id = 2
t=910409 [st= 198]    HTTP2_SESSION_RECV_DATA
                      --&gt; fin = false
                      --&gt; size = 1369
                      --&gt; stream_id = 2
t=910410 [st= 199]    HTTP2_SESSION_RECV_DATA
                      --&gt; fin = false
                      --&gt; size = 1369
                      --&gt; stream_id = 2</code></pre>
            <p>The web browser sends an <code>HTTP2_SESSION_SEND_HEADERS</code> request asking for the web page (that's the first item above) and subsequently receives the response headers and page followed immediately by two push promises for the two images. It then immediately starts receiving image data (see the <code>stream_id</code> 2).</p><p>CloudFlare's web server is pushing the image contents before the browser asked for them. When CloudFlare pushes an item specified in a <code>Link</code> header the header itself is stripped (to prevent the browser from re-requesting the resource).</p>
    <div>
      <h2>The Future Starts Here</h2>
      <a href="#the-future-starts-here">
        
      </a>
    </div>
    <p>We released HTTP/2 Server Push support to help kick start innovative use of this critical feature of HTTP/2. We would love people to start experimenting with it to see how much of a speed improvement is possible with their specific websites.</p><p>As can be seen from this blog post making use of Server Push in a web application is easy: just insert <code>Link</code> headers with the appropriate format. Server Push will be particularly fast if the items being pushed are also stored in CloudFlare's cache (as was the case with the examples above).</p><p>Please let us know how you use Server Push. We're particularly interested in experiences with pushing different types of resources (images vs. styles vs. scripts) and working out the optimal number of items to push (we currently allow up to 50 resources per page).</p> ]]></content:encoded>
            <category><![CDATA[HTTP2]]></category>
            <category><![CDATA[Server Push]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <guid isPermaLink="false">3nkrcgnTrCQJfUc4OyxsDC</guid>
            <dc:creator>John Graham-Cumming</dc:creator>
        </item>
        <item>
            <title><![CDATA[A Look at the New WordPress Brute Force Amplification Attack]]></title>
            <link>https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/</link>
            <pubDate>Fri, 16 Oct 2015 17:14:32 GMT</pubDate>
            <description><![CDATA[ Recently, a new brute force attack method for WordPress instances was identified by Sucuri. This latest technique allows attackers to try a large number of WordPress username and password login combinations in a single HTTP request. ]]></description>
            <content:encoded><![CDATA[ <p>Recently, a new brute force attack method for WordPress instances was identified by Sucuri. This latest technique allows attackers to try a large number of WordPress username and password login combinations in a single HTTP request.</p><p>The vulnerability can easily be abused by a simple script to try a significant number of username and password combinations with a relatively small number of HTTP requests. The following diagram shows a 4-fold increase in login attempts to HTTP requests, but this can trivially be expanded to a thousand logins.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6jdeVX3b5JHTMwZvY4J7wY/3db3519b348381101ef3bdd943e6e636/wordpress-xmlrpc-brute-force-amplification.png" />
            
            </figure><p>This form of brute force attack is harder to detect, since you won’t necessarily see a flood of requests. Fortunately, all CloudFlare paid customers have the option to enable a Web Application Firewall ruleset to stop this new attack method.</p>
    <div>
      <h3>What is XML-RPC?</h3>
      <a href="#what-is-xml-rpc">
        
      </a>
    </div>
    <p>To understand the vulnerability, it’s important to understand the basics of the XML remote procedure protocol (XML-RPC).</p><p>XML-RPC uses XML encoding over HTTP to provide a remote procedure call protocol. It’s commonly used to execute various <a href="https://codex.wordpress.org/XML-RPC_WordPress_API">functions in a WordPress instance</a> for APIs and other automated tasks. Requests that modify, manipulate, or view data using XML-RPC require user credentials with sufficient permissions.</p><p>Here is an example that requests a list of the user’s blogs:</p>
            <pre><code>&lt;?xml version="1.0" encoding="iso-8859-1"?&gt;
&lt;methodCall&gt;
&lt;methodName&gt;wp.getUsersBlogs&lt;/methodName&gt;
&lt;params&gt;
 &lt;param&gt;
  &lt;value&gt;
   &lt;string&gt;admin&lt;/string&gt;
  &lt;/value&gt;
 &lt;/param&gt;
 &lt;param&gt;
  &lt;value&gt;
   &lt;string&gt;password123&lt;/string&gt;
  &lt;/value&gt;
 &lt;/param&gt;
&lt;/params&gt;
&lt;/methodCall&gt;</code></pre>
            <p>The server responds with an XML message containing the requested information. The <code>isAdmin</code> name-value pair tells us our credentials were correct:</p>
            <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;methodResponse&gt;
&lt;params&gt;
	&lt;param&gt;
	  &lt;value&gt;
	  &lt;array&gt;&lt;data&gt;
	  &lt;value&gt;&lt;struct&gt;
	  &lt;member&gt;
		&lt;name&gt;isAdmin&lt;/name&gt;
		&lt;value&gt;&lt;boolean&gt;1&lt;/boolean&gt;&lt;/value&gt;
	  &lt;/member&gt;
	  &lt;member&gt;
		&lt;name&gt;url&lt;/name&gt;
		&lt;value&gt;&lt;string&gt;http://example.com/&lt;/string&gt;&lt;/value&gt;
	  &lt;/member&gt;
	  &lt;member&gt;
		&lt;name&gt;blogid&lt;/name&gt;
		&lt;value&gt;&lt;string&gt;1&lt;/string&gt;&lt;/value&gt;
	  &lt;/member&gt;
	  &lt;member&gt;
		&lt;name&gt;blogName&lt;/name&gt;
		&lt;value&gt;&lt;string&gt;testing&lt;/string&gt;&lt;/value&gt;
	  &lt;/member&gt;
	  &lt;member&gt;
		&lt;name&gt;xmlrpc&lt;/name&gt;
		&lt;value&gt;&lt;string&gt;http://example.com/xmlrpc.php&lt;/string&gt;&lt;/value&gt;
	  &lt;/member&gt;
	  &lt;/struct&gt;&lt;/value&gt;
	  &lt;/data&gt;&lt;/array&gt;
	  &lt;/value&gt;
	&lt;/param&gt;
&lt;/params&gt;
&lt;/methodResponse&gt;</code></pre>
            <p>As shown in this request, you must provide proper authentication to get a successful response. You can, in theory, create a script that tries different combinations of the username and password, but that is a noisy option that isn’t very effective and is easily detected (the server logs would show a flood of failed login attempts).</p><p>This is where the <code>system.multicall</code> functionality comes into play. You can run multiple methods with a single HTTP request. This is useful for mass editing blogs or deleting large numbers of comments, etc. Any method that requires authentication can be abused to brute force credentials. Here is what a sample XML <code>system.multicall</code> payload would look like:</p>
            <pre><code>&lt;?xml version="1.0"?&gt;
&lt;methodCall&gt;
&lt;methodName&gt;system.multicall&lt;/methodName&gt;
&lt;params&gt;
  &lt;param&gt;&lt;value&gt;&lt;array&gt;&lt;data&gt;
  &lt;value&gt;&lt;struct&gt;
  &lt;member&gt;
	&lt;name&gt;methodName&lt;/name&gt;
	&lt;value&gt;&lt;string&gt;wp.getUsersBlogs&lt;/string&gt;&lt;/value&gt;
  &lt;/member&gt;
  &lt;member&gt;
	&lt;name&gt;params&lt;/name&gt;&lt;value&gt;&lt;array&gt;&lt;data&gt;
	&lt;value&gt;&lt;array&gt;&lt;data&gt;
	&lt;value&gt;&lt;string&gt;admin&lt;/string&gt;&lt;/value&gt;
	&lt;value&gt;&lt;string&gt;password&lt;/string&gt;&lt;/value&gt;
	&lt;/data&gt;&lt;/array&gt;&lt;/value&gt;
	&lt;/data&gt;&lt;/array&gt;&lt;/value&gt;
  &lt;/member&gt;
  &lt;/struct&gt;&lt;/value&gt;
  &lt;value&gt;&lt;struct&gt;
  &lt;member&gt;
	&lt;name&gt;methodName&lt;/name&gt;
	&lt;value&gt;&lt;string&gt;wp.getUsersBlogs&lt;/string&gt;&lt;/value&gt;
  &lt;/member&gt;
  &lt;member&gt;
	&lt;name&gt;params&lt;/name&gt;
	&lt;value&gt;&lt;array&gt;&lt;data&gt;
	&lt;value&gt;&lt;array&gt;&lt;data&gt;
	  &lt;value&gt;&lt;string&gt;admin&lt;/string&gt;&lt;/value&gt;
	  &lt;value&gt;&lt;string&gt;password&lt;/string&gt;&lt;/value&gt;
	  &lt;/data&gt;&lt;/array&gt;&lt;/value&gt;
	&lt;/data&gt;&lt;/array&gt;&lt;/value&gt;
  &lt;/member&gt;
  &lt;/struct&gt;&lt;/value&gt;
  &lt;/data&gt;&lt;/array&gt;&lt;/value&gt;
  &lt;/param&gt;
&lt;/params&gt;
&lt;/methodCall&gt;</code></pre>
            <p>As you can see, this can lead to very obvious abuse.</p>
    <div>
      <h3>Exploitation</h3>
      <a href="#exploitation">
        
      </a>
    </div>
    <p>During testing, I was able to call the method <code>wp.getUserBlogs</code> 1,000 times in a single HTTP request (limited only by PHP memory issues). If a user creates a simple shell loop that executes one thousand times and runs a PHP script that crafts an HTTP request with one thousand method calls all requiring authentication, then that user would be able to try one million unique logins in a very short period of time.</p><p>This makes brute forcing the login very fast and can run down a pretty large wordlist in a short period of time. Also note that the <code>wp.getUserBlogs</code> method isn’t the only RPC call requiring authentication. It’s possible to use any RPC method which requires authentication to attempt logins and brute force the Wordpress credentials.</p>
    <div>
      <h3>CloudFlare Customers Are Protected</h3>
      <a href="#cloudflare-customers-are-protected">
        
      </a>
    </div>
    <p>When using CloudFlare with a Pro level plan or higher, you have the ability to turn on the Web Application Firewall (WAF) and take advantage of the new WordPress ruleset I created to mitigate this attack—all without any major interaction or supervision on your end.</p><p>Our WAF works by checking HTTP requests for consistencies that line up with known attacks and malicious activities. If a request does appear to be malicious, we drop it at the edge so it never even reaches the customer’s origin server.</p><p>To enable the rule, navigate to your CloudFlare Firewall dashboard, and reference the rule named "Blocks amplified brute force attempts to xmlrpc.php" with the rule ID WP0018.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4UjJTAaaGerRJ928DM0NGv/cc569d79f7675aafdf10d83ef73ea4cc/enabling-xmlrpc-waf-rule.png" />
            
            </figure><p>That’s all there is to it. Now you are protected from the new WordPress XML-RPC brute force amplification attack.</p>
    <div>
      <h3>The Manual Solution</h3>
      <a href="#the-manual-solution">
        
      </a>
    </div>
    <p>Another way to mitigate this attack is by disabling the ability to call the<code>system.multicall</code> method in your Wordpress installation by editing your<code>functions.php</code> file. Adding the function <code>mmx_remove_xmlrpc_methods()</code> will alleviate the problem, like so:</p>
            <pre><code>function mmx_remove_xmlrpc_methods( $methods ) {
	unset( $methods['system.multicall'] );
	return $methods;
}
add_filter( 'xmlrpc_methods', 'mmx_remove_xmlrpc_methods');</code></pre>
            
    <div>
      <h3>Final Thoughts</h3>
      <a href="#final-thoughts">
        
      </a>
    </div>
    <p>XML-RPC can be a useful tool for making changes to WordPress and other web applications; however, improper implementation of certain features can result in unintended consequences. Default-on methods like <code>system.multicall</code> and <code>pingback.ping</code> (we have a <a href="/wordpress-pingback-attacks-and-our-waf/">WAF rule for that one</a>, too) are just a few examples of possible exploits.</p><p>Properly configuring the CloudFlare Web Application Firewall for your Internet facing properties will protect you from such attacks with no changes to your server configuration.</p> ]]></content:encoded>
            <category><![CDATA[WordPress]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[Attacks]]></category>
            <category><![CDATA[Reliability]]></category>
            <guid isPermaLink="false">6gLyPOxWVFfvjEC44cNrMg</guid>
            <dc:creator>Pasha Kravtsov</dc:creator>
        </item>
        <item>
            <title><![CDATA[The CLAMP Stack]]></title>
            <link>https://blog.cloudflare.com/the-clamp-stack/</link>
            <pubDate>Mon, 15 Aug 2011 20:51:00 GMT</pubDate>
            <description><![CDATA[ Much of the modern Internet is built on what is known as the LAMP Stack. LAMP originally stood for Linux + Apache + MySQL + PHP. You'll find this basic stack at the core of companies like Google, Facebook, Yahoo, and more.  ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Much of the modern Internet is built on what is known as the LAMP Stack. LAMP originally stood for Linux + Apache + MySQL + PHP. You'll find this basic stack at the core of companies like Google, Facebook, Yahoo, and more. While some argue that the P should stand for Python, and we at CloudFlare tend to prefer Postgres to MySQL and NGINX to Apache, the general point is the same: with these free foundational tools, great Internet applications can be made.</p><p>We were tickled the other day when we heard someone giving a talk on the "CLAMP" stack: CloudFlare + LAMP. More and more companies are using CloudFlare to extend the scalability of the LAMP stack from an individual machine to a global network. CloudFlare cuts the load on a typical LAMP stack web server by nearly 70%. That allows you to put twice the load on a single server the LAMP burning out.</p><p>With integration into more and more of the <a href="http://www.cloudflare.com/hosting-partners">top hosting providers</a>, it's easier than ever to turn your LAMP into a CLAMP.</p> ]]></content:encoded>
            <category><![CDATA[php]]></category>
            <category><![CDATA[NGINX]]></category>
            <guid isPermaLink="false">6Zlw1FWfm0hsa5wYOuH9H2</guid>
            <dc:creator>Matthew Prince</dc:creator>
        </item>
    </channel>
</rss>