mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
Create gh-pages branch via GitHub
This commit is contained in:
+615
@@ -0,0 +1,615 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Gor by buger</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/normalize.css" media="screen">
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/stylesheet.css" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/github-light.css" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<section class="page-header">
|
||||
<h1 class="project-name">Gor</h1>
|
||||
<h2 class="project-tagline">Gor is an open-source tool for capturing and replaying live HTTP traffic into a test environment in order to continuously test your system with real data. It can be used to increase confidence in code deployments, configuration changes and infrastructure changes.</h2>
|
||||
<a href="https://github.com/buger/gor" class="btn">View on GitHub</a>
|
||||
<a href="https://github.com/buger/gor/zipball/master" class="btn">Download .zip</a>
|
||||
<a href="https://github.com/buger/gor/tarball/master" class="btn">Download .tar.gz</a>
|
||||
</section>
|
||||
|
||||
<section class="main-content">
|
||||
<h2>
|
||||
<a id="about" class="anchor" href="#about" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>About</h2>
|
||||
|
||||
<p>Gor is an open-source tool for capturing and replaying live HTTP traffic into a test environment in order to continuously test your system with real data. It can be used to increase confidence in code deployments, configuration changes and infrastructure changes.</p>
|
||||
|
||||
<p>Now you can test your code on real user sessions in an automated and repeatable fashion.
|
||||
<strong>No more falling down in production!</strong></p>
|
||||
|
||||
<p>Here is basic workflow: The listener server catches http traffic and sends it to the replay server or saves to file. The replay server forwards traffic to a given address.</p>
|
||||
|
||||
<p><img src="http://i.imgur.com/9mqj2SK.png" alt="Diagram"></p>
|
||||
|
||||
<h2>
|
||||
<a id="examples" class="anchor" href="#examples" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Examples</h2>
|
||||
|
||||
<h3>
|
||||
<a id="capture-traffic-from-port" class="anchor" href="#capture-traffic-from-port" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Capture traffic from port</h3>
|
||||
|
||||
<div class="highlight highlight-source-shell"><pre><span class="pl-c"># Run on servers where you want to catch traffic. You can run it on each `web` machine.</span>
|
||||
sudo gor --input-raw :80 --output-tcp replay.<span class="pl-k">local</span>:28020
|
||||
|
||||
<span class="pl-c"># Replay server (replay.local).</span>
|
||||
gor --input-tcp replay.<span class="pl-k">local</span>:28020 --output-http http://staging.com</pre></div>
|
||||
|
||||
<p>Since Gor use raw sockets to capture traffic it require <code>sudo</code> access. Alternatively you can allow access to raw sockets like this: <code>sudo setcap CAP_NET_RAW=ep gor</code></p>
|
||||
|
||||
<h3>
|
||||
<a id="using-1-gor-instance-for-both-listening-and-replaying" class="anchor" href="#using-1-gor-instance-for-both-listening-and-replaying" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Using 1 Gor instance for both listening and replaying</h3>
|
||||
|
||||
<p>It's recommended to use separate server for replaying traffic, but if you have enough CPU resources you can use single Gor instance.</p>
|
||||
|
||||
<pre><code>sudo gor --input-raw :80 --output-http "http://staging.com"
|
||||
</code></pre>
|
||||
|
||||
<h3>
|
||||
<a id="guarantee-of-replay-and-http-input" class="anchor" href="#guarantee-of-replay-and-http-input" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Guarantee of replay and HTTP input</h3>
|
||||
|
||||
<p>Due to how traffic interception works, there is chance of missing requests. If you want guarantee that requests will be replayed you can use http input, but it will require changes in your app as well. </p>
|
||||
|
||||
<pre><code>sudo gor --input-http :28019 --output-http "http://staging.com"
|
||||
</code></pre>
|
||||
|
||||
<p>Then in your application you should send copy (e.g. like reverse proxy) all incoming requests to Gor http input. </p>
|
||||
|
||||
<h2>
|
||||
<a id="configuration" class="anchor" href="#configuration" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Configuration</h2>
|
||||
|
||||
<h3>
|
||||
<a id="forward-to-multiple-addresses" class="anchor" href="#forward-to-multiple-addresses" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Forward to multiple addresses</h3>
|
||||
|
||||
<p>You can forward traffic to multiple endpoints. Just add multiple --output-* arguments.</p>
|
||||
|
||||
<pre><code>gor --input-tcp :28020 --output-http "http://staging.com" --output-http "http://dev.com"
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="splitting-traffic" class="anchor" href="#splitting-traffic" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Splitting traffic</h4>
|
||||
|
||||
<p>By default it will send same traffic to all outputs, but you have options to equally split it:</p>
|
||||
|
||||
<pre><code>gor --input-tcp :28020 --output-http "http://staging.com" --output-http "http://dev.com" --split-output true
|
||||
</code></pre>
|
||||
|
||||
<h3>
|
||||
<a id="http-output-workers" class="anchor" href="#http-output-workers" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>HTTP output workers</h3>
|
||||
|
||||
<p>By default Gor creates a dynamic pool of workers: it starts with 10 and creates more http output workers when the http output queue length is greater than 10. The number of workers created (N) is equal to the queue length at the time which it is checked and found to have a length greater than 10. The queue length is checked every time a message is written to the http output queue. No more workers will be spawned until that request to spawn N workers is satisfied. If a dynamic worker cannot process a message at that time, it will sleep for 100 milliseconds. If a dynamic worker cannot process a message for 2 seconds it dies.
|
||||
You may specify fixed number of workers using <code>--output-http-workers=20</code> option.</p>
|
||||
|
||||
<h3>
|
||||
<a id="follow-redirects" class="anchor" href="#follow-redirects" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Follow redirects</h3>
|
||||
|
||||
<p>By default Gor will ignore all redirects since they are handled by clients using your app, but in scenarios where your replayed environment introduces new redirects, you can enable them like this: </p>
|
||||
|
||||
<pre><code>gor --input-tcp replay.local:28020 --output-http http://staging.com --output-http-redirects 2
|
||||
</code></pre>
|
||||
|
||||
<p>The given example will follow up to 2 redirects per request.</p>
|
||||
|
||||
<h3>
|
||||
<a id="http-timeouts" class="anchor" href="#http-timeouts" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>HTTP timeouts</h3>
|
||||
|
||||
<p>By default http timeout for both request and response is 5 seconds. You can override it like this:</p>
|
||||
|
||||
<pre><code>gor --input-tcp replay.local:28020 --output-http http://staging.com --output-http-timeout 30s
|
||||
</code></pre>
|
||||
|
||||
<h3>
|
||||
<a id="rate-limiting" class="anchor" href="#rate-limiting" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Rate limiting</h3>
|
||||
|
||||
<p>Rate limiting can be useful if you only want to forward parts of production traffic and not overload your staging environment. There are 2 strategies: dropping random requests or dropping fractions of requests based on Header or URL param value. </p>
|
||||
|
||||
<h4>
|
||||
<a id="dropping-random-requests" class="anchor" href="#dropping-random-requests" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Dropping random requests</h4>
|
||||
|
||||
<p>Every input and output supports random rate limiting.
|
||||
There are 2 limiting algorithms: absolute or percentage based. </p>
|
||||
|
||||
<p>Absolute: If for current second it reached specified requests limit - disregard the rest, on next second counter reseted.</p>
|
||||
|
||||
<p>Percentage: For input-file it will slowdown or speedup request execution, for the rest it will use random generator to decide if request pass or not based on chance you specified. </p>
|
||||
|
||||
<p>You can specify your desired limit using the
|
||||
"|" operator after the server address:</p>
|
||||
|
||||
<h4>
|
||||
<a id="limiting-replay-using-absolute-number" class="anchor" href="#limiting-replay-using-absolute-number" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Limiting replay using absolute number</h4>
|
||||
|
||||
<pre><code># staging.server will not get more than 10 requests per second
|
||||
gor --input-tcp :28020 --output-http "http://staging.com|10"
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="limiting-listener-using-percentage-based-limiter" class="anchor" href="#limiting-listener-using-percentage-based-limiter" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Limiting listener using percentage based limiter</h4>
|
||||
|
||||
<pre><code># replay server will not get more than 10% of requests
|
||||
# useful for high-load environments
|
||||
gor --input-raw :80 --output-tcp "replay.local:28020|10%"
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="limiting-based-on-header-or-url-param-value" class="anchor" href="#limiting-based-on-header-or-url-param-value" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Limiting based on Header or URL param value</h4>
|
||||
|
||||
<p>If you have unique user id (like API key) stored in header or URL you can consistently forward specified percent of traffic only for fraction of this users.
|
||||
Basic formula looks like this: <code>FNV32-1A_hashing(value) % 100 >= chance</code>. Examples:</p>
|
||||
|
||||
<pre><code># Limit based on header value
|
||||
gor --input-raw :80 --output-tcp "replay.local:28020|10%" --http-header-limiter "X-API-KEY: 10%"
|
||||
|
||||
# Limit based on header value
|
||||
gor --input-raw :80 --output-tcp "replay.local:28020|10%" --http-param-limiter "api_key: 10%"
|
||||
</code></pre>
|
||||
|
||||
<p>Only percentage based limiting supported.</p>
|
||||
|
||||
<h3>
|
||||
<a id="filtering" class="anchor" href="#filtering" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Filtering</h3>
|
||||
|
||||
<h4>
|
||||
<a id="allow-url-regexp" class="anchor" href="#allow-url-regexp" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Allow url regexp</h4>
|
||||
|
||||
<pre><code># only forward requests being sent to the /api endpoint
|
||||
gor --input-raw :8080 --output-http staging.com --http-allow-url /api
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="disallow-url-regexp" class="anchor" href="#disallow-url-regexp" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Disallow url regexp</h4>
|
||||
|
||||
<pre><code># only forward requests NOT being sent to the /api... endpoint
|
||||
gor --input-raw :8080 --output-http staging.com --http-disallow-url /api
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="filter-based-on-regexp-of-header" class="anchor" href="#filter-based-on-regexp-of-header" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Filter based on regexp of header</h4>
|
||||
|
||||
<pre><code># only forward requests with an api version of 1.0x
|
||||
gor --input-raw :8080 --output-http staging.com --http-allow-header api-version:^1\.0\d
|
||||
|
||||
# only forward requests NOT containing User-Agent header value "Replayed by Gor"
|
||||
gor --input-raw :8080 --output-http staging.com --http-disallow-header "User-Agent: Replayed by Gor"
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="filter-based-on-http-method" class="anchor" href="#filter-based-on-http-method" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Filter based on http method</h4>
|
||||
|
||||
<p>Requests not matching a specified whitelist can be filtered out. For example to strip non-nullipotent requests:</p>
|
||||
|
||||
<pre><code>gor --input-raw :80 --output-http "http://staging.server" \
|
||||
--http-allow-method GET \
|
||||
--http-allow-method OPTIONS
|
||||
</code></pre>
|
||||
|
||||
<h3>
|
||||
<a id="rewriting-original-request" class="anchor" href="#rewriting-original-request" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Rewriting original request</h3>
|
||||
|
||||
<p>Gor supports some basic request rewriting support. For complex logic you can use middleware, see below.</p>
|
||||
|
||||
<h4>
|
||||
<a id="rewrite-url-based-on-a-mapping" class="anchor" href="#rewrite-url-based-on-a-mapping" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Rewrite URL based on a mapping</h4>
|
||||
|
||||
<pre><code># rewrite url to match the following
|
||||
gor --input-raw :8080 --output-http staging.com --http-rewrite-url /v1/user/([^\\/]+)/ping:/v2/user/$1/ping
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="set-url-param" class="anchor" href="#set-url-param" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Set URL param</h4>
|
||||
|
||||
<p>Set request url param, if param already exists it will be overwritten</p>
|
||||
|
||||
<pre><code>gor --input-raw :8080 --output-http staging.com --http-set-param api_key=1
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="set-header" class="anchor" href="#set-header" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Set Header</h4>
|
||||
|
||||
<p>Set request header, if header already exists it will be overwritten. This may be useful if you need to identify requests generated by Gor or enable feature flagged functionality in an application:</p>
|
||||
|
||||
<pre><code>gor --input-raw :80 --output-http "http://staging.server" \
|
||||
--http-header "User-Agent: Replayed by Gor" \
|
||||
--http-header "Enable-Feature-X: true"
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="host-header" class="anchor" href="#host-header" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Host header</h4>
|
||||
|
||||
<p>Host header gets special treatment. By default Host get set to the value specified in --output-http. If you manually set --http-header "Host: anonther.com", Gor will not override Host value.</p>
|
||||
|
||||
<p>If you app accepts traffic from multiple domain, and you want to keep original headers, there is specific <code>--http-original-host</code> with tells Gor do not touch Host header at all.</p>
|
||||
|
||||
<h3>
|
||||
<a id="middleware" class="anchor" href="#middleware" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Middleware</h3>
|
||||
|
||||
<p>Middleware is a program that accepts request and response payload at STDIN and emits modified requests at STDOUT. You can implement any custom logic like stripping private data, advanced rewriting, support for oAuth and etc.</p>
|
||||
|
||||
<pre><code> Original request +--------------+
|
||||
+-------------+----------STDIN---------->+ |
|
||||
| Gor input | | Middleware |
|
||||
+-------------+----------STDIN---------->+ |
|
||||
Original response +------+---+---+
|
||||
| ^
|
||||
+-------------+ Modified request v |
|
||||
| Gor output +<---------STDOUT-----------------+ |
|
||||
+-----+-------+ |
|
||||
| |
|
||||
| Replayed response |
|
||||
+------------------STDIN----------------->----+
|
||||
</code></pre>
|
||||
|
||||
<p>Middleware can be written in any language, see <code>examples/middleware</code> folder for examples.
|
||||
Middleware program should accept the fact that all communication with Gor is asynchronous, there is no guarantee that original request and response messages will come one after each other. Your app should take care of the state if logic depends on original or replayed response, see <code>examples/middleware/token_modifier.go</code> as example.</p>
|
||||
|
||||
<p>Simple bash echo middleware (returns same request) will look like this:</p>
|
||||
|
||||
<div class="highlight highlight-source-shell"><pre><span class="pl-k">while</span> <span class="pl-c1">read</span> line<span class="pl-k">;</span> <span class="pl-k">do</span>
|
||||
<span class="pl-c1">echo</span> <span class="pl-smi">$line</span>
|
||||
end</pre></div>
|
||||
|
||||
<p>Middleware can be enabled using <code>--middleware</code> option, by specifying path to executable file:</p>
|
||||
|
||||
<pre><code>gor --input-raw :80 --middleware "/opt/middleware_executable" --output-http "http://staging.server"
|
||||
</code></pre>
|
||||
|
||||
<h4>
|
||||
<a id="communication-protocol" class="anchor" href="#communication-protocol" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Communication protocol</h4>
|
||||
|
||||
<p>All messages should be hex encoded, new line character specifieds the end of the message, eg. new message per line.</p>
|
||||
|
||||
<p>Decoded payload consist of 2 parts: header and HTTP payload, separated by new line character. </p>
|
||||
|
||||
<p>Example request payload:</p>
|
||||
|
||||
<pre><code>1 932079936fa4306fc308d67588178d17d823647c 1439818823587396305
|
||||
GET /a HTTP/1.1
|
||||
Host: 127.0.0.1
|
||||
|
||||
</code></pre>
|
||||
|
||||
<p>Example response payload:</p>
|
||||
|
||||
<pre><code>2 8e091765ae902fef8a2b7d9dd960e9d52222bd8c 2782013
|
||||
HTTP/1.1 200 OK
|
||||
Date: Mon, 17 Aug 2015 13:40:23 GMT
|
||||
Content-Length: 0
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
</code></pre>
|
||||
|
||||
<p>Header contains request meta information separated by spaces. First value is payload type, possible values: <code>1</code> - request, <code>2</code> - original response, <code>3</code> - replayed response.
|
||||
Next goes request id: unique among all requests (sha1 of time and Ack), but remain same for original and replayed response, so you can create associations between request and responses. Third argument varies depending on payload type: for request - start time, for responses - round-trip time.</p>
|
||||
|
||||
<p>HTTP payload is unmodified HTTP requests/responses intercepted from network. You can read more about request format <a href="http://www.jmarshall.com/easy/http/">here</a>, <a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol">here</a> and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">here</a>. You can operate with payload as you want, add headers, change path, and etc. Basically you just editing a string, just ensure that it is RCF compliant.</p>
|
||||
|
||||
<p>At the end modified (or untouched) request should be emitted back to STDOUT, keeping original header, and hex-encoded. If you want to filter request, just not send it. Emitting responses back is required, even if you did not touch them.</p>
|
||||
|
||||
<h4>
|
||||
<a id="advanced-example" class="anchor" href="#advanced-example" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Advanced example</h4>
|
||||
|
||||
<p>Imagine that you have auth system that randomly generate access tokens, which used later for accessing secure content. Since there is no pre-defined token value, naive approach without middleware (or if middleware use only request payloads) will fail, because replayed server have own tokens, not synced with origin. To fix this, our middleware should take in account responses of replayed and origin server, store <code>originalToken -> replayedToken</code> aliases and rewrite all requests using this token to use replayed alias. See <code>examples/middleware/token_modifier.go</code> and <code>middleware_test.go#TestTokenMiddleware</code> as example of described scheme.</p>
|
||||
|
||||
<h3>
|
||||
<a id="saving-requests-to-file-and-replaying-them" class="anchor" href="#saving-requests-to-file-and-replaying-them" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Saving requests to file and replaying them</h3>
|
||||
|
||||
<p>You can save requests to file, and replay them later:</p>
|
||||
|
||||
<pre><code># write to file
|
||||
gor --input-raw :80 --output-file requests.gor
|
||||
|
||||
# read from file
|
||||
gor --input-file requests.gor --output-http "http://staging.com"
|
||||
</code></pre>
|
||||
|
||||
<p><strong>Note:</strong> Replay will preserve the original time differences between requests.</p>
|
||||
|
||||
<h3>
|
||||
<a id="load-testing" class="anchor" href="#load-testing" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Load testing</h3>
|
||||
|
||||
<p>Currently it supported only by <code>input-file</code> and only when using percentage based limiter. Unlike default limiter for <code>input-file</code> instead of dropping requests it will slowdown or speedup request emitting. Note that unlike examples above limiter is applied to input:</p>
|
||||
|
||||
<pre><code># Replay from file on 2x speed
|
||||
gor --input-file "requests.gor|200%" --output-http "staging.com"
|
||||
</code></pre>
|
||||
|
||||
<h3>
|
||||
<a id="basic-auth" class="anchor" href="#basic-auth" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Basic Auth</h3>
|
||||
|
||||
<p>If your development or staging environment is protected by Basic Authentication then those credentials can be injected in during the replay:</p>
|
||||
|
||||
<pre><code>gor --input-raw :80 --output-http "http://user:pass@staging .com"
|
||||
</code></pre>
|
||||
|
||||
<p>Note: This will overwrite any Authorization headers in the original request.</p>
|
||||
|
||||
<h2>
|
||||
<a id="stats" class="anchor" href="#stats" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Stats</h2>
|
||||
|
||||
<p>Gor can report stats on the <code>output-tcp</code> and <code>output-http</code> request queues. Stats are reported to the console every 5 seconds in the form <code>latest,mean,max,count,count/second</code> by using the <code>--output-http-stats</code> and <code>--output-tcp-stats</code> options.</p>
|
||||
|
||||
<p>Examples:</p>
|
||||
|
||||
<pre><code>2014/04/23 21:17:50 output_tcp:latest,mean,max,count,count/second
|
||||
2014/04/23 21:17:50 output_tcp:0,0,0,0,0
|
||||
2014/04/23 21:17:55 output_tcp:1,1,2,68,13
|
||||
2014/04/23 21:18:00 output_tcp:1,1,2,92,18
|
||||
2014/04/23 21:18:05 output_tcp:1,1,2,119,23
|
||||
</code></pre>
|
||||
|
||||
<pre><code>Version: 0.8
|
||||
2014/04/23 21:19:46 output_http:latest,mean,max,count,count/second
|
||||
2014/04/23 21:19:46 output_http:0,0,0,0,0
|
||||
2014/04/23 21:19:51 output_http:0,0,0,0,0
|
||||
2014/04/23 21:19:56 output_http:0,0,0,0,0
|
||||
2014/04/23 21:20:01 output_http:1,0,1,50,10
|
||||
2014/04/23 21:20:06 output_http:1,1,4,72,14
|
||||
2014/04/23 21:20:11 output_http:1,0,1,179,35
|
||||
2014/04/23 21:20:16 output_http:1,0,1,148,29
|
||||
2014/04/23 21:20:21 output_http:1,1,2,91,18
|
||||
2014/04/23 21:20:26 output_http:1,1,2,150,30
|
||||
2014/04/23 21:18:15 output_http:100,99,100,70,14
|
||||
2014/04/23 21:18:21 output_http:100,99,100,55,11
|
||||
</code></pre>
|
||||
|
||||
<h3>
|
||||
<a id="how-can-i-tell-if-i-have-bottlenecks" class="anchor" href="#how-can-i-tell-if-i-have-bottlenecks" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>How can I tell if I have bottlenecks?</h3>
|
||||
|
||||
<p>Key areas that sometimes experience bottlenecks are the output-tcp and output-http functions which have internal queues for requests. Each queue has an upper limit of 100. Enable stats reporting to see if any queues are experiencing bottleneck behavior.</p>
|
||||
|
||||
<h4>
|
||||
<a id="output-http-bottlenecks" class="anchor" href="#output-http-bottlenecks" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>output-http bottlenecks</h4>
|
||||
|
||||
<p>When running a Gor replay the output-http feature may bottleneck if:</p>
|
||||
|
||||
<ul>
|
||||
<li>the replay has inadequate bandwidth. If the replay is receiving or sending more messages than its network adapter can handle the output-http-stats may report that the output-http queue is filling up. See if there is a way to upgrade the replay's bandwidth.</li>
|
||||
<li>with <code>--output-http-workers</code> set to anything other than <code>-1</code> the <code>-output-http</code> target is unable to respond to messages in a timely manner. The http output workers which take messages off the output-http queue, process the request, and ensure that the request did not result in an error may not be able to keep up with the number of incoming requests. If the replay is not using dynamic worker scaling (<code>--output-http-workers=-1</code>) The optimal number of output-http-workers can be determined with the formula <code>output-workers = (Average number of requests per second)/(Average target response time per second)</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h4>
|
||||
<a id="output-tcp-bottlenecks" class="anchor" href="#output-tcp-bottlenecks" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>output-tcp bottlenecks</h4>
|
||||
|
||||
<p>When using the Gor listener the output-tcp feature may bottleneck if:</p>
|
||||
|
||||
<ul>
|
||||
<li>the replay is unable to accept and process more requests than the listener is able generate. Prior to troubleshooting the output-tcp bottleneck, ensure that the replay target is not experiencing any bottlenecks. </li>
|
||||
<li>the replay target has inadequate bandwidth to handle all its incoming requests. If a replay target's incoming bandwidth is maxed out the output-tcp-stats may report that the output-tcp queue is filling up. See if there is a way to upgrade the replay's bandwidth.</li>
|
||||
</ul>
|
||||
|
||||
<h3>
|
||||
<a id="elasticsearch" class="anchor" href="#elasticsearch" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>ElasticSearch</h3>
|
||||
|
||||
<p>For deep response analyze based on url, cookie, user-agent and etc. you can export response metadata to ElasticSearch. See <a href="ELASTICSEARCH.md">ELASTICSEARCH.md</a> for more details.</p>
|
||||
|
||||
<pre><code>gor --input-tcp :80 --output-http "http://staging.com" --output-http-elasticsearch "es_host:api_port/index_name"
|
||||
</code></pre>
|
||||
|
||||
<h2>
|
||||
<a id="additional-help" class="anchor" href="#additional-help" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Additional help</h2>
|
||||
|
||||
<p>Feel free to ask question directly by email or by creating github issue.</p>
|
||||
|
||||
<h2>
|
||||
<a id="latest-releases-including-binaries" class="anchor" href="#latest-releases-including-binaries" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Latest releases (including binaries)</h2>
|
||||
|
||||
<p><a href="https://github.com/buger/gor/releases">https://github.com/buger/gor/releases</a></p>
|
||||
|
||||
<h2>
|
||||
<a id="command-line-reference" class="anchor" href="#command-line-reference" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Command line reference</h2>
|
||||
|
||||
<p><code>gor -h</code> output:</p>
|
||||
|
||||
<pre><code> -http-allow-header=[]: A regexp to match a specific header against. Requests with non-matching headers will be dropped:
|
||||
gor --input-raw :8080 --output-http staging.com --http-allow-header api-version:^v1
|
||||
-http-disallow-header=[]: A regexp to match a specific header against. Requests with matching headers will be dropped:
|
||||
gor --input-raw :8080 --output-http staging.com --http-disallow-header "User-Agent: Replayed by Gor"
|
||||
-http-allow-method=[]: Whitelist of HTTP methods to replay. Anything else will be dropped:
|
||||
gor --input-raw :8080 --output-http staging.com --http-allow-method GET --http-allow-method OPTIONS
|
||||
-http-allow-url=[]: A regexp to match requests against. Filter get matched agains full url with domain. Anything else will be dropped:
|
||||
gor --input-raw :8080 --output-http staging.com --http-allow-url ^www.
|
||||
-http-disallow-url=[]: A regexp to match requests against. Filter get matched agains full url with domain. Anything else will be forwarded:
|
||||
gor --input-raw :8080 --output-http staging.com --http-disallow-url ^www.
|
||||
-http-header-limiter=[]: Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific header:
|
||||
gor --input-raw :8080 --output-http staging.com --http-header-imiter user-id:25%
|
||||
-http-param-limiter=[]: Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific GET param:
|
||||
gor --input-raw :8080 --output-http staging.com --http-param-limiter user_id:25%
|
||||
-http-rewrite-url=[]: Rewrite the request url based on a mapping:
|
||||
gor --input-raw :8080 --output-http staging.com --http-rewrite-url /v1/user/([^\/]+)/ping:/v2/user/$1/ping
|
||||
-http-set-header=[]: Inject additional headers to http reqest:
|
||||
gor --input-raw :8080 --output-http staging.com --http-set-header 'User-Agent: Gor'
|
||||
-http-set-param=[]: Set request url param, if param already exists it will be overwritten:
|
||||
gor --input-raw :8080 --output-http staging.com --http-set-param api_key=1
|
||||
-input-dummy=[]: Used for testing outputs. Emits 'Get /' request every 1s
|
||||
-input-file=[]: Read requests from file:
|
||||
gor --input-file ./requests.gor --output-http staging.com
|
||||
-input-http=[]: Read requests from HTTP, should be explicitly sent from your application:
|
||||
# Listen for http on 9000
|
||||
gor --input-http :9000 --output-http staging.com
|
||||
-input-raw=[]: Capture traffic from given port (use RAW sockets and require *sudo* access):
|
||||
# Capture traffic from 8080 port
|
||||
gor --input-raw :8080 --output-http staging.com
|
||||
-input-tcp=[]: Used for internal communication between Gor instances. Example:
|
||||
# Receive requests from other Gor instances on 28020 port, and redirect output to staging
|
||||
gor --input-tcp :28020 --output-http staging.com
|
||||
-memprofile="": write memory profile to this file
|
||||
-output-dummy=[]: Used for testing inputs. Just prints data coming from inputs.
|
||||
-output-file=[]: Write incoming requests to file:
|
||||
gor --input-raw :80 --output-file ./requests.gor
|
||||
-output-http=[]: Forwards incoming requests to given http address.
|
||||
# Redirect all incoming requests to staging.com address
|
||||
gor --input-raw :80 --output-http http://staging.com
|
||||
-output-http-elasticsearch="": Send request and response stats to ElasticSearch:
|
||||
gor --input-raw :8080 --output-http staging.com --output-http-elasticsearch 'es_host:api_port/index_name'
|
||||
-output-http-header-filter=[]: WARNING: `--output-http-header-filter` DEPRECATED, use `--http-allow-header` instead
|
||||
-output-http-redirects=0: Enable how often redirects should be followed.
|
||||
-output-http-stats=false: Report http output queue stats to console every 5 seconds. Remember to include also `--stats`
|
||||
-output-http-workers=0: Gor uses dynamic worker scaling by default. Enter a number to run a set number of workers.
|
||||
-output-tcp=[]: Used for internal communication between Gor instances. Example:
|
||||
# Listen for requests on 80 port and forward them to other Gor instance on 28020 port
|
||||
gor --input-raw :80 --output-tcp replay.local:28020
|
||||
-output-tcp-stats=false: Report TCP output queue stats to console every 5 seconds. Remember to include also `--stats`
|
||||
-split-output=false: By default each output gets same traffic. If set to `true` it splits traffic equally among all outputs.
|
||||
-stats=false: Turn on queue stats output. Use in combination with the other *-stats flags.
|
||||
-verbose=false: Turn on verbose/debug output
|
||||
</code></pre>
|
||||
|
||||
<h2>
|
||||
<a id="building-from-source" class="anchor" href="#building-from-source" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Building from source</h2>
|
||||
|
||||
<ol>
|
||||
<li>Setup standard Go environment <a href="http://golang.org/doc/code.html">http://golang.org/doc/code.html</a> and ensure that $GOPATH environment variable properly set.</li>
|
||||
<li>
|
||||
<code>go get github.com/buger/gor</code>.</li>
|
||||
<li><code>cd $GOPATH/src/github.com/buger/gor</code></li>
|
||||
<li>
|
||||
<code>go build</code> to get binary, or <code>go test</code> to run tests</li>
|
||||
</ol>
|
||||
|
||||
<h2>
|
||||
<a id="development" class="anchor" href="#development" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Development</h2>
|
||||
|
||||
<p>Project contains Docker environment.</p>
|
||||
|
||||
<ol>
|
||||
<li>Build container: <code>make dbuild</code>
|
||||
</li>
|
||||
<li>Run all tests: <code>make dtest</code>. Run specific test: <code>make dtest ARGS=-test.run=**regexp**</code>
|
||||
</li>
|
||||
<li>Bash access to container: <code>make dbash</code>. Inside container you have python to run simple web server <code>python -m SimpleHTTPServer 8080</code> and <code>curl</code> to make http requests. </li>
|
||||
</ol>
|
||||
|
||||
<h2>
|
||||
<a id="questions-and-support" class="anchor" href="#questions-and-support" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Questions and support</h2>
|
||||
|
||||
<p>All bug-reports and suggestions should go though Github Issues or our <a href="https://groups.google.com/forum/#!forum/gor-users">Google Group</a>. Or you can just send email to <a href="mailto:gor-users@googlegroups.com">gor-users@googlegroups.com</a></p>
|
||||
|
||||
<p>If you have some private questions you can send direct mail to <a href="mailto:leonsbox@gmail.com">leonsbox@gmail.com</a></p>
|
||||
|
||||
<h2>
|
||||
<a id="faq" class="anchor" href="#faq" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>FAQ</h2>
|
||||
|
||||
<h3>
|
||||
<a id="what-os-are-supported" class="anchor" href="#what-os-are-supported" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>What OS are supported?</h3>
|
||||
|
||||
<p>For now only Linux based. *BSD (including MacOS is not supported yet, check <a href="https://github.com/buger/gor/issues/22">https://github.com/buger/gor/issues/22</a> for details)</p>
|
||||
|
||||
<h3>
|
||||
<a id="why-does-the---input-raw-requires-sudo-or-root-access" class="anchor" href="#why-does-the---input-raw-requires-sudo-or-root-access" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Why does the <code>--input-raw</code> requires sudo or root access?</h3>
|
||||
|
||||
<p>Listener works by sniffing traffic from a given port. It's accessible
|
||||
only by using sudo or root access.</p>
|
||||
|
||||
<h3>
|
||||
<a id="how-do-you-deal-with-user-session-to-replay-the-traffic-correctly" class="anchor" href="#how-do-you-deal-with-user-session-to-replay-the-traffic-correctly" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>How do you deal with user session to replay the traffic correctly?</h3>
|
||||
|
||||
<p>You can rewrite session related headers/params to match your staging environment. If you require custom logic (e.g random token based auth) follow this discussion: <a href="https://github.com/buger/gor/issues/154">https://github.com/buger/gor/issues/154</a></p>
|
||||
|
||||
<h3>
|
||||
<a id="can-i-use-gor-to-intercept-ssl-traffic" class="anchor" href="#can-i-use-gor-to-intercept-ssl-traffic" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Can i use Gor to intercept SSL traffic?</h3>
|
||||
|
||||
<p>Basic idea is that SSL was made to protect itself from traffic interception. There 2 options:
|
||||
1. Move SSL handling to proxy like Nginx or Amazon ELB. And allow Gor to listen on upstreams.
|
||||
2. Use <code>--input-http</code> so you can duplicate request payload directly from your app to Gor, but it will require your app modifications.</p>
|
||||
|
||||
<p>More can be find here: <a href="https://github.com/buger/gor/issues/85">https://github.com/buger/gor/issues/85</a></p>
|
||||
|
||||
<h3>
|
||||
<a id="is-there-a-limit-for-size-of-http-request-when-using-output-http" class="anchor" href="#is-there-a-limit-for-size-of-http-request-when-using-output-http" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Is there a limit for size of HTTP request when using output-http?</h3>
|
||||
|
||||
<p>Due to the fact that Gor can't guarantee interception of all packets, for large payloads > 200kb there is chance of missing some packets and corrupting body. Treat it as a feature and chance to test broken bodies handling :)
|
||||
The only way to guarantee delivery is using <code>--input-http</code>, but you will miss some features.</p>
|
||||
|
||||
<h3>
|
||||
<a id="im-getting-too-many-open-files-error" class="anchor" href="#im-getting-too-many-open-files-error" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>I'm getting 'too many open files' error</h3>
|
||||
|
||||
<p>Typical linux shell has a small open files soft limit at 1024. You can easily raise that when you do this before starting your gor replay process:</p>
|
||||
|
||||
<p>ulimit -n 64000</p>
|
||||
|
||||
<p>More about ulimit: <a href="http://www.thecodingmachine.com/solving-the-too-many-open-files-exception-in-red5-or-any-other-application/">http://www.thecodingmachine.com/solving-the-too-many-open-files-exception-in-red5-or-any-other-application/</a></p>
|
||||
|
||||
<h3>
|
||||
<a id="the-cpu-average-across-my-load-balanced-targets-is-higher-than-the-source" class="anchor" href="#the-cpu-average-across-my-load-balanced-targets-is-higher-than-the-source" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>The CPU average across my load-balanced targets is higher than the source</h3>
|
||||
|
||||
<p>If you are replaying traffic from multiple listeners to a load-balanced target and you use sticky sessions, you may observe that the target servers have a higher CPU load than the listener servers. This may be because the sticky session cookie of the original load balancer is not honored by the target load balancer thus resulting in requests that would normally hit the same target server hitting different servers on the backend thus reducing some caching benefits gained via the load balancing. Try running just one listener against one replay target and see if the CPU utilization comparison is more accurate.</p>
|
||||
|
||||
<h2>
|
||||
<a id="tuning" class="anchor" href="#tuning" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Tuning</h2>
|
||||
|
||||
<p>To achieve the top most performance you should tune the source server system limits:</p>
|
||||
|
||||
<pre><code>net.ipv4.tcp_max_tw_buckets = 65536
|
||||
net.ipv4.tcp_tw_recycle = 1
|
||||
net.ipv4.tcp_tw_reuse = 0
|
||||
net.ipv4.tcp_max_syn_backlog = 131072
|
||||
net.ipv4.tcp_syn_retries = 3
|
||||
net.ipv4.tcp_synack_retries = 3
|
||||
net.ipv4.tcp_retries1 = 3
|
||||
net.ipv4.tcp_retries2 = 8
|
||||
net.ipv4.tcp_rmem = 16384 174760 349520
|
||||
net.ipv4.tcp_wmem = 16384 131072 262144
|
||||
net.ipv4.tcp_mem = 262144 524288 1048576
|
||||
net.ipv4.tcp_max_orphans = 65536
|
||||
net.ipv4.tcp_fin_timeout = 10
|
||||
net.ipv4.tcp_low_latency = 1
|
||||
net.ipv4.tcp_syncookies = 0
|
||||
</code></pre>
|
||||
|
||||
<h2>
|
||||
<a id="contributing" class="anchor" href="#contributing" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Contributing</h2>
|
||||
|
||||
<ol>
|
||||
<li>Fork it</li>
|
||||
<li>Create your feature branch (git checkout -b my-new-feature)</li>
|
||||
<li>Commit your changes (git commit -am 'Added some feature')</li>
|
||||
<li>Push to the branch (git push origin my-new-feature)</li>
|
||||
<li>Create new Pull Request</li>
|
||||
</ol>
|
||||
|
||||
<h2>
|
||||
<a id="companies-using-gor" class="anchor" href="#companies-using-gor" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Companies using Gor</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://www.gov.uk">GOV.UK</a> - UK Government Digital Service</li>
|
||||
<li>
|
||||
<a href="http://theguardian.com">theguardian.com</a> - Most popular online newspaper in the UK</li>
|
||||
<li>
|
||||
<a href="http://www.tomtom.com/">TomTom</a> - Global leader in navigation, traffic and map products, GPS Sport Watches and fleet management solutions.</li>
|
||||
<li>
|
||||
<a href="http://www.3scale.net/">3SCALE</a> - API infrastructure to manage your APIs for internal or external users</li>
|
||||
<li>
|
||||
<a href="http://www.opinionlab.com">Optionlab</a> - Optimize customer experience and drive engagement across multiple channels</li>
|
||||
<li>
|
||||
<a href="http://tubemogul.com">TubeMogul</a> - Software for Brand Advertising</li>
|
||||
<li>
|
||||
<a href="http://www.videologygroup.com/">Videology</a> - Video advertising platform</li>
|
||||
<li>
|
||||
<a href="http://foreksmobile.com/">ForeksMobile</a> - One of the leading financial application development company in Turkey</li>
|
||||
<li>
|
||||
<a href="http://granify.com">Granify</a> - AI backed SaaS solution that enables online retailers to maximise their sales</li>
|
||||
<li>And many more!</li>
|
||||
</ul>
|
||||
|
||||
<p>If you are using Gor we are happy add you to the list and share your story, just write to: <a href="mailto:leonsbox@gmail.com">leonsbox@gmail.com</a></p>
|
||||
|
||||
<footer class="site-footer">
|
||||
<span class="site-footer-owner"><a href="https://github.com/buger/gor">Gor</a> is maintained by <a href="https://github.com/buger">buger</a>.</span>
|
||||
|
||||
<span class="site-footer-credits">This page was generated by <a href="https://pages.github.com">GitHub Pages</a> using the <a href="https://github.com/jasonlong/cayman-theme">Cayman theme</a> by <a href="https://twitter.com/jasonlong">Jason Long</a>.</span>
|
||||
</footer>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 GitHub, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
.pl-c /* comment */ {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,
|
||||
.pl-s .pl-v /* string variable */ {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.pl-e /* entity */,
|
||||
.pl-en /* entity.name */ {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.pl-s .pl-s1 /* string source */,
|
||||
.pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pl-ent /* entity.name.tag */ {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.pl-k /* keyword, storage, storage.type */ {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.pl-pds /* punctuation.definition.string, string.regexp.character-class */,
|
||||
.pl-s /* string */,
|
||||
.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,
|
||||
.pl-sr /* string.regexp */,
|
||||
.pl-sr .pl-cce /* string.regexp constant.character.escape */,
|
||||
.pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */,
|
||||
.pl-sr .pl-sre /* string.regexp source.ruby.embedded */ {
|
||||
color: #183691;
|
||||
}
|
||||
|
||||
.pl-v /* variable */ {
|
||||
color: #ed6a43;
|
||||
}
|
||||
|
||||
.pl-id /* invalid.deprecated */ {
|
||||
color: #b52a1d;
|
||||
}
|
||||
|
||||
.pl-ii /* invalid.illegal */ {
|
||||
background-color: #b52a1d;
|
||||
color: #f8f8f8;
|
||||
}
|
||||
|
||||
.pl-sr .pl-cce /* string.regexp constant.character.escape */ {
|
||||
color: #63a35c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pl-ml /* markup.list */ {
|
||||
color: #693a17;
|
||||
}
|
||||
|
||||
.pl-mh /* markup.heading */,
|
||||
.pl-mh .pl-en /* markup.heading entity.name */,
|
||||
.pl-ms /* meta.separator */ {
|
||||
color: #1d3e81;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pl-mq /* markup.quote */ {
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
.pl-mi /* markup.italic */ {
|
||||
color: #333;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.pl-mb /* markup.bold */ {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pl-md /* markup.deleted, meta.diff.header.from-file */ {
|
||||
background-color: #ffecec;
|
||||
color: #bd2c00;
|
||||
}
|
||||
|
||||
.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {
|
||||
background-color: #eaffea;
|
||||
color: #55a532;
|
||||
}
|
||||
|
||||
.pl-mdr /* meta.diff.range */ {
|
||||
color: #795da3;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pl-mo /* meta.output */ {
|
||||
color: #1d3e81;
|
||||
}
|
||||
|
||||
Vendored
+424
@@ -0,0 +1,424 @@
|
||||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
||||
* and Firefox.
|
||||
* Correct `block` display not defined for `main` in IE 11.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9/10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow not hidden in IE 9/10/11.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contain overflow in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address odd `em`-unit font size rendering in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||
* styling of `select`, unless a `border` property is set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited.
|
||||
* Known issue: affects color of disabled elements.
|
||||
* 2. Correct font properties not being inherited.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||
* Correct `select` style inheritance in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's recommended that you don't attempt to style these elements.
|
||||
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
||||
*
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
||||
* `font-size` values of the `input`, it causes the cursor style of the
|
||||
* decrement button to change from `default` to `text`.
|
||||
*/
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */ /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||
* padding (and `textfield` appearance).
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't inherit the `font-weight` (applied by a rule above).
|
||||
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
||||
*/
|
||||
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
* {
|
||||
box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #606c71; }
|
||||
|
||||
a {
|
||||
color: #1e6bb8;
|
||||
text-decoration: none; }
|
||||
a:hover {
|
||||
text-decoration: underline; }
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
margin-bottom: 1rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 0.3rem;
|
||||
transition: color 0.2s, background-color 0.2s, border-color 0.2s; }
|
||||
.btn + .btn {
|
||||
margin-left: 1rem; }
|
||||
|
||||
.btn:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.3); }
|
||||
|
||||
@media screen and (min-width: 64em) {
|
||||
.btn {
|
||||
padding: 0.75rem 1rem; } }
|
||||
|
||||
@media screen and (min-width: 42em) and (max-width: 64em) {
|
||||
.btn {
|
||||
padding: 0.6rem 0.9rem;
|
||||
font-size: 0.9rem; } }
|
||||
|
||||
@media screen and (max-width: 42em) {
|
||||
.btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.9rem; }
|
||||
.btn + .btn {
|
||||
margin-top: 1rem;
|
||||
margin-left: 0; } }
|
||||
|
||||
.page-header {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background-color: #159957;
|
||||
background-image: linear-gradient(120deg, #155799, #159957); }
|
||||
|
||||
@media screen and (min-width: 64em) {
|
||||
.page-header {
|
||||
padding: 5rem 6rem; } }
|
||||
|
||||
@media screen and (min-width: 42em) and (max-width: 64em) {
|
||||
.page-header {
|
||||
padding: 3rem 4rem; } }
|
||||
|
||||
@media screen and (max-width: 42em) {
|
||||
.page-header {
|
||||
padding: 2rem 1rem; } }
|
||||
|
||||
.project-name {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.1rem; }
|
||||
|
||||
@media screen and (min-width: 64em) {
|
||||
.project-name {
|
||||
font-size: 3.25rem; } }
|
||||
|
||||
@media screen and (min-width: 42em) and (max-width: 64em) {
|
||||
.project-name {
|
||||
font-size: 2.25rem; } }
|
||||
|
||||
@media screen and (max-width: 42em) {
|
||||
.project-name {
|
||||
font-size: 1.75rem; } }
|
||||
|
||||
.project-tagline {
|
||||
margin-bottom: 2rem;
|
||||
font-weight: normal;
|
||||
opacity: 0.7; }
|
||||
|
||||
@media screen and (min-width: 64em) {
|
||||
.project-tagline {
|
||||
font-size: 1.25rem; } }
|
||||
|
||||
@media screen and (min-width: 42em) and (max-width: 64em) {
|
||||
.project-tagline {
|
||||
font-size: 1.15rem; } }
|
||||
|
||||
@media screen and (max-width: 42em) {
|
||||
.project-tagline {
|
||||
font-size: 1rem; } }
|
||||
|
||||
.main-content :first-child {
|
||||
margin-top: 0; }
|
||||
.main-content img {
|
||||
max-width: 100%; }
|
||||
.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: normal;
|
||||
color: #159957; }
|
||||
.main-content p {
|
||||
margin-bottom: 1em; }
|
||||
.main-content code {
|
||||
padding: 2px 4px;
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 0.9rem;
|
||||
color: #383e41;
|
||||
background-color: #f3f6fa;
|
||||
border-radius: 0.3rem; }
|
||||
.main-content pre {
|
||||
padding: 0.8rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
color: #567482;
|
||||
word-wrap: normal;
|
||||
background-color: #f3f6fa;
|
||||
border: solid 1px #dce6f0;
|
||||
border-radius: 0.3rem; }
|
||||
.main-content pre > code {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: #567482;
|
||||
word-break: normal;
|
||||
white-space: pre;
|
||||
background: transparent;
|
||||
border: 0; }
|
||||
.main-content .highlight {
|
||||
margin-bottom: 1rem; }
|
||||
.main-content .highlight pre {
|
||||
margin-bottom: 0;
|
||||
word-break: normal; }
|
||||
.main-content .highlight pre, .main-content pre {
|
||||
padding: 0.8rem;
|
||||
overflow: auto;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
border-radius: 0.3rem; }
|
||||
.main-content pre code, .main-content pre tt {
|
||||
display: inline;
|
||||
max-width: initial;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: initial;
|
||||
line-height: inherit;
|
||||
word-wrap: normal;
|
||||
background-color: transparent;
|
||||
border: 0; }
|
||||
.main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after {
|
||||
content: normal; }
|
||||
.main-content ul, .main-content ol {
|
||||
margin-top: 0; }
|
||||
.main-content blockquote {
|
||||
padding: 0 1rem;
|
||||
margin-left: 0;
|
||||
color: #819198;
|
||||
border-left: 0.3rem solid #dce6f0; }
|
||||
.main-content blockquote > :first-child {
|
||||
margin-top: 0; }
|
||||
.main-content blockquote > :last-child {
|
||||
margin-bottom: 0; }
|
||||
.main-content table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: normal;
|
||||
word-break: keep-all; }
|
||||
.main-content table th {
|
||||
font-weight: bold; }
|
||||
.main-content table th, .main-content table td {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #e9ebec; }
|
||||
.main-content dl {
|
||||
padding: 0; }
|
||||
.main-content dl dt {
|
||||
padding: 0;
|
||||
margin-top: 1rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold; }
|
||||
.main-content dl dd {
|
||||
padding: 0;
|
||||
margin-bottom: 1rem; }
|
||||
.main-content hr {
|
||||
height: 2px;
|
||||
padding: 0;
|
||||
margin: 1rem 0;
|
||||
background-color: #eff0f1;
|
||||
border: 0; }
|
||||
|
||||
@media screen and (min-width: 64em) {
|
||||
.main-content {
|
||||
max-width: 64rem;
|
||||
padding: 2rem 6rem;
|
||||
margin: 0 auto;
|
||||
font-size: 1.1rem; } }
|
||||
|
||||
@media screen and (min-width: 42em) and (max-width: 64em) {
|
||||
.main-content {
|
||||
padding: 2rem 4rem;
|
||||
font-size: 1.1rem; } }
|
||||
|
||||
@media screen and (max-width: 42em) {
|
||||
.main-content {
|
||||
padding: 2rem 1rem;
|
||||
font-size: 1rem; } }
|
||||
|
||||
.site-footer {
|
||||
padding-top: 2rem;
|
||||
margin-top: 2rem;
|
||||
border-top: solid 1px #eff0f1; }
|
||||
|
||||
.site-footer-owner {
|
||||
display: block;
|
||||
font-weight: bold; }
|
||||
|
||||
.site-footer-credits {
|
||||
color: #819198; }
|
||||
|
||||
@media screen and (min-width: 64em) {
|
||||
.site-footer {
|
||||
font-size: 1rem; } }
|
||||
|
||||
@media screen and (min-width: 42em) and (max-width: 64em) {
|
||||
.site-footer {
|
||||
font-size: 1rem; } }
|
||||
|
||||
@media screen and (max-width: 42em) {
|
||||
.site-footer {
|
||||
font-size: 0.9rem; } }
|
||||
Reference in New Issue
Block a user