<?xml version="1.0" encoding="UTF-8"?><rss 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" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[blog.chib.me]]></title><description><![CDATA[Let's code something cool 😎]]></description><link>http://blog.chib.me/</link><image><url>http://blog.chib.me/favicon.png</url><title>blog.chib.me</title><link>http://blog.chib.me/</link></image><generator>Ghost 1.16</generator><lastBuildDate>Sun, 30 Mar 2025 18:13:20 GMT</lastBuildDate><atom:link href="http://blog.chib.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Story of retry]]></title><description><![CDATA[<div class="kg-card-markdown"><h5 id="day1thebeginning">Day 1. The beginning.</h5>
<p> <br>
👨 <strong>John</strong><br>
We need to make an http service for getting user name by its id.<br>
🤓 <strong>Carl</strong><br>
Oh, but we already have one, it is <a href="http://internal.com">http://internal.com</a>. Look:</p>
<pre><code>$ curl -XPOST http://internal.com/1
✅ HTTP/1.1 200 OK
John

$ curl -XPOST http://internal.com/2</code></pre></div>]]></description><link>http://blog.chib.me/story-of-retry/</link><guid isPermaLink="false">59f600d150b3c800013c3a4d</guid><dc:creator><![CDATA[Gennady Chibisov]]></dc:creator><pubDate>Sat, 09 Sep 2017 15:17:39 GMT</pubDate><media:content url="http://blog.chib.me/content/images/2017/09/road-sign-1274312_1920.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><h5 id="day1thebeginning">Day 1. The beginning.</h5>
<img src="http://blog.chib.me/content/images/2017/09/road-sign-1274312_1920.jpg" alt="Story of retry"><p> <br>
👨 <strong>John</strong><br>
We need to make an http service for getting user name by its id.<br>
🤓 <strong>Carl</strong><br>
Oh, but we already have one, it is <a href="http://internal.com">http://internal.com</a>. Look:</p>
<pre><code>$ curl -XPOST http://internal.com/1
✅ HTTP/1.1 200 OK
John

$ curl -XPOST http://internal.com/2
✅ HTTP/1.1 200 OK
Carl
</code></pre>
<p>👨 <strong>John</strong><br>
Yeh, but it’s internal api. We want to make requests from the internet.<br>
🤓 <strong>Carl</strong><br>
Then we need some authorization for it.<br>
👨 <strong>John</strong><br>
Let’s make it dead simple for now and send the password as a request parameter.<br>
🤓 <strong>Carl</strong><br>
Ok, wait a minute, I’ll write some code.</p>
<pre><code class="language-python">import requests
from flask import Flask, Response

app = Flask('service')

@app.route(&quot;/&quot;)
def get(request):
    if request.args['p'] != '123':
        return Response('Wrong password', status=401)
    else:
        user_id = request.args['u']
        response = requests.post('http://internal.com/' + user_id)
        response.raise_for_status()
        return Response(response.json()['name'])
</code></pre>
<p>🤓 <strong>Carl</strong><br>
Save...Deploy...Done. With bad password:</p>
<pre><code>$ curl http://service.com/?p=111&amp;u=1
❌ HTTP/1.1 401 Unauthorized
Wrong password
</code></pre>
<p>🤓 <strong>Carl</strong><br>
With good password:</p>
<pre><code>$ curl http://service.com/?p=123&amp;u=1
✅ HTTP/1.1 200 OK
John
</code></pre>
<p>👨 <strong>John</strong><br>
Cool!</p>
<h4 id="day2fivehundredproblemsandagirlaintone">Day 2. Five hundred problems and a girl ain't one.</h4>
<p> <br>
👨 <strong>John</strong><br>
Houston, we have a problem! Our new service is not working!<br>
🤓 <strong>Carl</strong><br>
What? Wait I minute, I’ll check:</p>
<pre><code>$ curl http://service.com/?p=123&amp;u=1
❌ HTTP/1.1 500 Internal Error
</code></pre>
<p>🤓 <strong>Carl</strong><br>
Oh shit! I’ll look at logs:</p>
<pre><code>❌ Response from http://internal.com/1 is 500 Internal server error
</code></pre>
<p>🤓 <strong>Carl</strong><br>
The internal service responds with an error. I’ll talk to them.</p>
<p>...<em>Carl switches to the chat with the internal service developer</em>...</p>
<p>🤓 <strong>Carl</strong><br>
Hey man. What’s wrong with your service? It responds with an error and crushes our service?<br>
👷 <strong>Rob</strong><br>
Hi. We have database issues. Sometimes we respond with the error but sometimes without the error. Look:</p>
<pre><code>$ curl -XPOST http://internal.com/1
❌ HTTP/1.1 500 Internal Error

$ curl -XPOST http://internal.com/1
❌ HTTP/1.1 500 Internal Error

$ curl -XPOST http://internal.com/1
✅ HTTP/1.1 200 OK
John
</code></pre>
<p>🤓 <strong>Carl</strong><br>
And what should I do?<br>
👷 <strong>Rob</strong><br>
Man, networks are unreliable. Make some retry requests and get the successful one.<br>
🤓 <strong>Carl</strong><br>
Ok, wait a minute.</p>
<pre><code class="language-diff">-import requests
+from requests.adapters import HTTPAdapter
+from requests import Session
+from requests.packages.urllib3.util.retry import Retry
from flask import Flask, Response

app = Flask('service')

+session = Session()
+retry = Retry(
+    total=5,
+    method_whitelist=['POST'],
+    status_forcelist=[500]
+)
+session.mount(
+    'http://',
+    HTTPAdapter(max_retries=retry)
+)

@app.route(&quot;/&quot;)
def get(request):
    password = request.args['p']
    if password != '123':
        return Response('Wrong password', status=401)
    else:
        user_id = request.args['u']
-       response = requests.post('http://internal.com/' + user_id)
+       response = session.post('http://internal/' + user_id)
        response.raise_for_status()
        return Response(response.json()['name'])
</code></pre>
<p>🤓 <strong>Carl</strong><br>
Save...Deploy...Done. Let's try.</p>
<pre><code>$ curl http://service.com/?p=123&amp;u=1
✅ HTTP/1.1 200 OK
John

$ curl http://service.com/?p=123&amp;u=1
✅ HTTP/1.1 200 OK
John

$ curl http://service.com/?p=123&amp;u=1
✅ HTTP/1.1 200 OK
John
</code></pre>
<p>🤓 <strong>Carl</strong><br>
3 of 3! And in the logs I see the retry requests. Thank you!</p>
<pre><code>⚠️ Retrying because response is 500.
⚠️ Retrying because response is 500.
⚠️ Retrying because response is 500.
✅ Response from http://internal.com/1 is 200 OK
</code></pre>
<p>...<em>Carl switches to the chat with John</em>...</p>
<p>🤓 <strong>Carl</strong><br>
I’ve fixed the problem. The internal service has some database issues. I've added 5 retries for every request and we are working normally now.</p>
<p>👨 <strong>John</strong><br>
Nice!</p>
<h4 id="day3knowyourlimits">Day 3. Know your limits.</h4>
<p> <br>
👨 <strong>John</strong><br>
Houston, we have a problem! Our new service is not working!<br>
🤓 <strong>Carl</strong><br>
What, again? Arrr, let’s look at logs.</p>
<pre><code>❌ Response from http://internal.com/1 is 429 Too Many Requests
</code></pre>
<p>...<em>Carl switches to the chat with the internal service developer</em>...</p>
<p>🤓 <strong>Carl</strong><br>
Hey man, we have a problem with your service again. It responds with 429 error. What is it?<br>
👷 <strong>Rob</strong><br>
Hi. You're making too many requests. Do you still retrying the requests?<br>
🤓 <strong>Carl</strong><br>
Yep.<br>
👷 <strong>Rob</strong><br>
What is your backoff factor?<br>
🤓 <strong>Carl</strong><br>
Ehhhh, what is my what?<br>
👷 <strong>Rob</strong><br>
I mean, you can do only 100 requests per second to our service. You make a request, get the error with status code 500 (yes, we still have the database issues) and then make another request immediately. Just sleep after the 500 error for a while.<br>
🤓 <strong>Carl</strong><br>
Let's see.</p>
<pre><code class="language-diff">from requests.adapters import HTTPAdapter
from requests import Session
from requests.packages.urllib3.util.retry import Retry
from flask import Flask, Response

app = Flask('service')

session = Session()
retry = Retry(
    total=5,
    method_whitelist=['POST'],
-   status_forcelist=[500],
+   status_forcelist=[500, 429],
+   backoff_factor=0.1
)
session.mount(
    'http://',
    HTTPAdapter(max_retries=retry)
)

@app.route(&quot;/&quot;)
def get(request):
    password = request.args['password']
    if password != 'password123':
        return Response('Wrong password', status=401)
    else:
        user_id = request.args['user_id']
        response = session.post('http://internal/' + user_id)
        response.raise_for_status()
        return Response(response.json()['name'])
</code></pre>
<p>🤓 <strong>Carl</strong><br>
I've added backoff factor equals to 0.1. As I understand it will sleep for [0.0s, 0.2s, 0.4s, ...] between retries. Also I've added retry for 429 error. Save...Deploy...Done. Let's try.</p>
<pre><code>$ curl http://service.com/?p=123&amp;u=1
✅ HTTP/1.1 200 OK
John

$ curl http://service.com/?p=123&amp;u=1
✅ HTTP/1.1 200 OK
John

$ curl http://service.com/?p=123&amp;u=1
✅ HTTP/1.1 200 OK
John
</code></pre>
<p>🤓 <strong>Carl</strong><br>
3 of 3! Thanks!</p>
<h4 id="day4yourtimeisout">Day 4. Your time is out.</h4>
<p> <br>
👨 <strong>John</strong><br>
Houston, we have a problem. Our service is working but response is too slow!<br>
🤓 <strong>Carl</strong><br>
What? Let's look...Hmm, yeah. It’s approx 5 seconds per request response. What do you want?<br>
👨 <strong>John</strong><br>
We need minimum 100ms.</p>
<p>🤓 ➜ 👷</p>
<p>🤓 <strong>Carl</strong><br>
Hey man, we have a problem with your service again. It responds too slow and that’s why our service responds too slow.<br>
👷 <strong>Rob</strong><br>
We have some balancer issue. Usually it routes all the requests to the closest data center. But now it started to route half of the requests to the furthest.<br>
🤓 <strong>Carl</strong><br>
What should I do?<br>
👷 <strong>Rob</strong><br>
Networks are unreliable, dude. You can set the timeout for the request. I mean, if the request didn’t finished in 30ms try to make another request. It should help.</p>
<pre><code class="language-diff">from requests.adapters import HTTPAdapter
from requests import Session
from requests.packages.urllib3.util.retry import Retry
from flask import Flask, Response

app = Flask('service')

session = Session()
retry = Retry(
    total=5,
    method_whitelist=['POST'],
    status_forcelist=[500, 429],
    backoff_factor=0.1
)
session.mount(
    'http://',
    HTTPAdapter(max_retries=retry)
)

@app.route(&quot;/&quot;)
def get(request):
    password = request.args['password']
    if password != 'password123':
        return Response('Wrong password', status=401)
    else:
        user_id = request.args['user_id']
-       response = session.post('http://internal/' + user_id)
+       response = session.post(
+           'http://internal/' + user_id,
+           timeout=0.3
+       )
        response.raise_for_status()
        return Response(response.json()['name'])
</code></pre>
<p>Yes, it helped. Thank you.</p>
<pre><code>⚠️ Retrying. Connection timed out (0.3).
⚠️ Retrying. Connection timed out (0.3).
✅ Response from http://internal.com/1 is 200 OK
</code></pre>
<h4 id="day5timeoutsstrikeback">Day 5. Timeouts strike back.</h4>
<p> <br>
👨 <strong>John</strong><br>
Houston, we have a problem. Your service is not working!<br>
🤓 <strong>Carl</strong><br>
I know, I know...</p>
<pre><code>⚠️ Retrying. Connection timed out (0.3).
⚠️ Retrying. Connection timed out (0.3).
⚠️ Retrying. Connection timed out (0.3).
⚠️ Retrying. Connection timed out (0.3).
⚠️ Retrying. Connection timed out (0.3).
❌ Max retries exceeded.
</code></pre>
<p>🤓 ➜ 👷</p>
<p>🤓 <strong>Carl</strong><br>
Dude, we have a problem. Your service is not responding in 30ms. We make 5 retry requests and then give up.<br>
👷 <strong>Rob</strong><br>
We are upgrading the service and some instances have connectivity issues. In some cases it can not even start any connections. But you can detect that your request came to the bad instance. Our networks are quite fast and should establish connections in 5ms. Just split the timeout to the connection and response read.<br>
🤓 <strong>Carl</strong><br>
But I'll still have 5 retry requests.<br>
👷 <strong>Rob</strong><br>
As I remember your service must return the response in 100ms. It's <code>25ms*2 + 5ms*10</code>.<br>
🤓 <strong>Carl</strong><br>
Yeh, I got it. I'll make 2 retries for 25ms read timeouts and 10 retries for 5ms connect timeouts.</p>
<pre><code class="language-diff">from requests.adapters import HTTPAdapter
from requests import Session
from requests.packages.urllib3.util.retry import Retry
from flask import Flask, Response

app = Flask('service')

session = Session()
retry = Retry(
    total=5,
    method_whitelist=['POST'],
    status_forcelist=[500, 429],
    backoff_factor=0.1,
+   connect=10,
+   read=2,
)
session.mount(
    'http://',
    HTTPAdapter(max_retries=retry)
)

@app.route(&quot;/&quot;)
def get(request):
    password = request.args['password']
    if password != 'password123':
        return Response('Wrong password', status=401)
    else:
        user_id = request.args['user_id']
        response = session.post(
            'http://internal/' + user_id,
-           timeout=0.3
+           timeout=(
+               0.05,  # connect timeout
+               0.25,  # read timeout
+           )
        )
        response.raise_for_status()
        return Response(response.json()['name'])
</code></pre>
<p>🤓 <strong>Carl</strong><br>
It works! Thanks!</p>
<pre><code>⚠️ Retrying. Connection timed out (0.05).
⚠️ Retrying. Connection timed out (0.05).
⚠️ Retrying. Connection timed out (0.05).
⚠️ Retrying. Connection timed out (0.05).
⚠️ Retrying. Connection timed out (0.05).
⚠️ Retrying. Connection timed out (0.25).
✅ Response from http://internal.com/1 is 200 OK
</code></pre>
<h4 id="day6stopthismadness">Day 6. Stop this madness!</h4>
<p> <br>
👨 <strong>John</strong><br>
Houston, we have not such a big problem by the problem it is.<br>
🤓 <strong>Carl</strong><br>
???<br>
👨 <strong>John</strong><br>
When we make a request with not existing user id we get 500 error. It would be awesome to have 404 error. Can you help us?</p>
<pre><code>$ curl http://service.com/?p=123&amp;u=999
❌ HTTP/1.1 500 Internal server error
</code></pre>
<p>🤓 <strong>Carl</strong><br>
I see. That's because the retries are exceeded.</p>
<pre><code>⚠️ Retrying because response is 500.
⚠️ Retrying because response is 500.
⚠️ Retrying because response is 500.
⚠️ Retrying because response is 500.
⚠️ Retrying because response is 500.
❌ Max retries exceeded.
</code></pre>
<p>🤓 ➜ 👷</p>
<p>🤓 <strong>Carl</strong><br>
Hey man, your service constantly returns 500 error for not existing user. We make 5 retry requests and then give up.<br>
👷 <strong>Rob</strong><br>
It’s not a bug, it’s a feature. It’s part of the protocol and can not be fixed. You always can check the response. You can stop doing retries based on it. Just stop the retry cycle if the response is <code>User not exists</code>.</p>
<pre><code>$ curl http://internal.com/999
❌ HTTP/1.1 500 Internal server error
User not exists.
</code></pre>
<p>🤓 <strong>Carl</strong><br>
Let's see... We're always make the retry request when you return 500 error. It's impossible to check the response via the default <code>Retry</code> class. I'll have to extend it.</p>
<pre><code class="language-diff">from requests.adapters import HTTPAdapter
from requests import Session
from requests.packages.urllib3.util.retry import Retry
from flask import Flask, Response
+from requests.packages.urllib3 import (
+    exceptions as urllib3_exceptions
+)

+class MyRetry(Retry):
+    def increment(self, *args, **kwargs):
+        if (kwargs.get('response') and 
+            kwargs['response'].status == 500 and 
+            'User not exists' in kwargs['response'].data):
+            raise urllib3_exceptions.MaxRetryError(
+                pool=kwargs.get('_pool'),
+                url=args[1],
+                reason=urllib3_exceptions.ResponseError(
+                    'User not exists.'
+                )
+            )
+        return super(MyRetry, self).increment(*args, **kwargs)
    

app = Flask('service')

session = Session()
-retry = Retry(
+retry = MyRetry(
    total=5,
    method_whitelist=['POST'],
    status_forcelist=[500, 429],
    backoff_factor=0.1,
    connect=10,
    read=2,
+   raise_on_status=False,
)
session.mount(
    'http://',
    HTTPAdapter(max_retries=retry)
)

@app.route(&quot;/&quot;)
def get(request):
    password = request.args['password']
    if password != 'password123':
        return Response('Wrong password', status=401)
    else:
        user_id = request.args['user_id']
        response = session.post(
            'http://internal/' + user_id,
            timeout=(
                0.05,  # socket timeout
                0.25,  # read timeout
            )
        )
-       response.raise_for_status()
-       return Response(response.json()['name'])
+       if response.ok:
+           return Response(response.json()['name'])
+       elif response.content == 'User not exists':
+           return Response('User not exists', status=404)
</code></pre>
<p>🤓 <strong>Carl</strong><br>
It works!</p>
<pre><code>$ curl http://service.com/?p=123&amp;u=999
✅ HTTP/1.1 404 Not found
User not exists
</code></pre>
<p>🤓 <strong>Carl</strong><br>
We're not retrying on <code>User not exists</code> response and return 404 right away.</p>
<h4 id="threemonthslater">Three months later.</h4>
<p> <br>
👨 <strong>John</strong><br>
Hey dude. It’s been 3 months as your service works without any error. You rock, man!<br>
🤓 <strong>Carl</strong><br>
Thank you, it was a hard work to make it happen.<br>
👨 <strong>John</strong><br>
Actually we have some problem. Someone hacked our system and downloaded all the names of our customers. What do you think? How did they do so?<br>
🤓 <strong>Carl</strong><br>
Mmm, didn’t you told anyone the password to the service?<br>
👨 <strong>John</strong><br>
No, I didn’t!<br>
🤓 <strong>Carl</strong><br>
It’s better to change the password I believe. Let’s try. Change… Save… Deploy… Done! It’s <code>123456</code> now!<br>
👨 <strong>John</strong><br>
You’re the cyber security ninja, man!<br>
🤓 <strong>Carl</strong><br>
💪</p>
<p>The end.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Golang: Http Testing Case ( gorilla/mux )]]></title><description><![CDATA[<div class="kg-card-markdown"><p>In our projects we have a lot of tests. Http Api tests are one of the most important parts of any testing.<br>
Error Messages, Status Codes and Responses should be tested very careful.</p>
<p>This article contains:</p>
<ul>
<li>Preparations</li>
<li>Test case</li>
<li>The magic function</li>
<li>The test</li>
<li>Conclusion</li>
</ul>
<p>We will use a <code>github.</code></p></div>]]></description><link>http://blog.chib.me/golang-http-testing-case/</link><guid isPermaLink="false">59f600d050b3c800013c3a43</guid><category><![CDATA[deform]]></category><dc:creator><![CDATA[Andrey Chibisov]]></dc:creator><pubDate>Mon, 25 Apr 2016 13:09:47 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>In our projects we have a lot of tests. Http Api tests are one of the most important parts of any testing.<br>
Error Messages, Status Codes and Responses should be tested very careful.</p>
<p>This article contains:</p>
<ul>
<li>Preparations</li>
<li>Test case</li>
<li>The magic function</li>
<li>The test</li>
<li>Conclusion</li>
</ul>
<p>We will use a <code>github.com/gorilla/mux</code> for url routing.</p>
<h4 id="preparations">Preparations</h4>
<p>We are going to test a <code>User</code> handler. A handler which responses with a <code>User</code> profile.</p>
<pre><code class="language-go">func User(w http.ResponseWriter, r *http.Request) {
    ...
}
</code></pre>
<p>The router:</p>
<pre><code class="language-go">import (
	&quot;github.com/gorilla/mux&quot;
)

func NewRouter() (s *mux.Router) {
	s = mux.NewRouter()

	userHandler := s.PathPrefix(&quot;/user/&quot;).Subrouter()
	userHandler.HandleFunc(&quot;/&quot;, UserRegister).Methods(&quot;POST&quot;).Name(&quot;UserRegister&quot;)
	userHandler.HandleFunc(&quot;/{user_id}/&quot;, User).Methods(&quot;GET&quot;, &quot;HEAD&quot;).Name(&quot;User&quot;)

	return s
}
</code></pre>
<h4 id="testcase">Test case</h4>
<p>In our test case we need to start a <code>httptest.NewServer</code> which will use a router:</p>
<pre><code class="language-go">import (
    ...
    &quot;net/http/httptest&quot;
    &quot;net/url&quot;
	&quot;testing&quot;
	...
)

func TestUserRegistration(t *testing.T) {
    router := NewRouter()
    localServer := httptest.NewServer(router)
    defer localServer.Close()
    localServerUrl, err := url.Parse(localServer.URL)

	...
}
</code></pre>
<p>We've got a <code>localServerUrl</code> which points to our endpont.</p>
<h4 id="themagicfunction">The magic function</h4>
<p>We need to form a <code>url</code> for committing a request. Let's write a function for it</p>
<pre><code class="language-go">import (
	&quot;github.com/gorilla/mux&quot;
	&quot;net/url&quot;
)

func MakeUrl(server_url *url.URL, router *mux.Router, urlName string, url_params []string, query_params map[string]string) (*url.URL, error) {
	//  Get url from routing
	urlPtr, err := router.Get(urlName).URL(
		url_params...,
	)
	if err != nil {
		return nil, err
	}

	//  Get currently serving httptest
	url_I := *server_url
	//  Join it's address and path of a specific url
	url_I.Path = urlPtr.Path
	q := url_I.Query()
	for k, v := range query_params {
		q.Set(k, v)
	}
	url_I.RawQuery = q.Encode()

	return &amp;url_I, err
}
</code></pre>
<p>This function makes <code>Just Do It</code> with an url :D</p>
<p>To form a url for getting a user's profile you need to call it:</p>
<pre><code class="language-go">    url, err := MakeUrl(
		localServerUrl,
		router,
		&quot;UserProfile&quot;,        // &lt;- HANDLER's NAME
		[]string{
			&quot;user_id&quot;, 1234,  // &lt;- HANDLER's URL PARAMS:   /users/123/
		},
		map[string]string{}{
			&quot;format&quot;: &quot;json&quot;, // &lt;- HANDLER URL QUERY:      /users/123/?format=json
		}, 
	)
	Expect(err).NotTo(HaveOccurred())
</code></pre>
<p>Let's assume we have a complicated handler</p>
<pre><code class="language-go">
    entityHandler.HandleFunc(&quot;/{entity_id}/{entity_property:(.*[\\\\/]?)}/&quot;, http_handlers.PutEntityProperty()).Methods(&quot;PUT&quot;, &quot;OPTIONS&quot;).Name(&quot;PutEntityProperty&quot;)

</code></pre>
<p>To make a request you need to</p>
<pre><code class="language-go">    url, err := MakeUrl(
		localServerUrl,
		router,
		&quot;PutEntityProperty&quot;,
		[]string{
			&quot;entity_id&quot;, &quot;users&quot;,
			&quot;entity_property&quot;: &quot;email&quot;
		},
		map[string]string{}{
		}, 
	)
	Expect(err).NotTo(HaveOccurred())
</code></pre>
<h4 id="thetest">The test</h4>
<p>Our test will look like</p>
<pre><code class="language-go">	email := generated_data[&quot;email&quot;]
	url, err := test_helpers.MakeUrl(
		localServerUrl,
		router,
		&quot;UserRegister&quot;,
		[]string{},
		map[string]string{},
	)

	request := &amp;TestHttpRequest{}
	err := request.Post(
		url.String(),
		map[string]interface{}{
			&quot;Content-Type&quot;: &quot;application/json&quot;
		},
		map[string]interface{}{
			&quot;email&quot;:    email,
			&quot;password&quot;: generated_data[&quot;password&quot;],
		},
	)
</code></pre>
<h4 id="conclusion">Conclusion</h4>
<p>We have an easy http handlers testing. Just pass url's <code>name</code>, <code>params</code> and <code>query params</code> to function. In case if your endpoints will change - there is no need in editing a lot of tests to rename url :D</p>
</div>]]></content:encoded></item><item><title><![CDATA[Golang: simple map operations]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Hi there, today we'd like to introduce you a <a href="https://github.com/firewut/go-json-map">package</a> which will help you to deal some maps in a <code>json path</code> way.</p>
<p>Let's assume you have a response from some service:</p>
<pre><code class="language-go">var document map[string]interface{}
err := json.Unmarshal(someCrazyResponse, &amp;document)
fmt.Println(document)
</code></pre>
<p>and your <code>document</code> map</p></div>]]></description><link>http://blog.chib.me/golang-maps-case/</link><guid isPermaLink="false">59f600d050b3c800013c3a42</guid><category><![CDATA[deform]]></category><dc:creator><![CDATA[Andrey Chibisov]]></dc:creator><pubDate>Wed, 02 Mar 2016 12:35:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Hi there, today we'd like to introduce you a <a href="https://github.com/firewut/go-json-map">package</a> which will help you to deal some maps in a <code>json path</code> way.</p>
<p>Let's assume you have a response from some service:</p>
<pre><code class="language-go">var document map[string]interface{}
err := json.Unmarshal(someCrazyResponse, &amp;document)
fmt.Println(document)
</code></pre>
<p>and your <code>document</code> map is</p>
<pre><code class="language-go">map[string]interface{}{
	&quot;one&quot;: []map[string]interface{}{
		{
			&quot;two&quot;: []map[string]interface{}{
				{&quot;three&quot;: &quot;got three&quot;},
				{&quot;four&quot;: &quot;got four&quot;},
			},
		},
		{
			&quot;two&quot;: []map[string]interface{}{
				{&quot;five&quot;: &quot;got five&quot;},
				{&quot;six&quot;: &quot;got six&quot;},
			},
		},
		{
			&quot;two&quot;: []map[string]interface{}{
				{&quot;seven&quot;: &quot;got seven&quot;},
				{&quot;eight&quot;: &quot;got eight&quot;},
			},
		},
		{
			&quot;three&quot;: []map[string]interface{}{
				{&quot;four&quot;: map[string]interface{}{
					&quot;five&quot;: &quot;six&quot;,
				}},
				{&quot;seven&quot;: map[string]interface{}{
					&quot;eight&quot;: &quot;ten&quot;,
				}},
			},
		},
	},
}
</code></pre>
<p>You need to extract a property located in <code>one[2].two[1].eight</code> which is currently <code>ten</code></p>
<p>In golang it's a quite long string:</p>
<pre><code class="language-go">myVar := document[&quot;one&quot;].([]map[string]interface{})[2][&quot;two&quot;].([]map[string]interface{})[1][&quot;eight&quot;]
</code></pre>
<p>It also can panic because in some of these documents there is no such property <code>one[3].two</code></p>
<h4 id="thepackage">The package</h4>
<p>So, we have developed a <a href="https://github.com/firewut/go-json-map">go-json-map</a> package.</p>
<p>Using it you can:</p>
<ul>
<li>
<p><strong>get</strong> a property</p>
<pre><code>  myVar, err := GetProperty(document, &quot;one[2].two[1].eight&quot;)
</code></pre>
</li>
<li>
<p><strong>create</strong> some property which does not exist in a document</p>
<pre><code>  err := CreateProperty(document, &quot;path.which.does.not.exist&quot;, &quot;some value&quot;)

  err := CreateProperty(document, &quot;path/which/does/not/exist/separated/using/slashes/&quot;, &quot;some value&quot;, &quot;/&quot;)
</code></pre>
</li>
<li>
<p><strong>update</strong> missing or existing property</p>
<pre><code>  err := UpdateProperty(document, &quot;path.which.should.be.updated&quot;, &quot;some value&quot;)

  err := UpdateProperty(document, &quot;path/which/does/not/exist/separated/using/slashes/&quot;, &quot;some value&quot;, &quot;/&quot;)

  err := UpdateProperty(document, &quot;one[2].two[1].eight&quot;,  &quot;eleven&quot;)
  
  err := UpdateProperty(document, &quot;one[3].three[2].four&quot;, []int{1,2,3,4})
</code></pre>
</li>
<li>
<p><strong>delete</strong> a property</p>
<pre><code>  err := DeleteProperty(document, &quot;one[3].three[1].seven.eight&quot;)
</code></pre>
</li>
</ul>
<h4 id="conclusion">Conclusion</h4>
<p>This package allows you to work with maps a bit easier :D</p>
<h4 id="links">Links:</h4>
<ul>
<li><a href="https://github.com/firewut/go-json-map">Package</a></li>
<li><a href="https://godoc.org/github.com/firewut/go-json-map">Documentation</a></li>
</ul>
</div>]]></content:encoded></item><item><title><![CDATA[How to convert databases with one line of code]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Have you ever wanted to convert mysql database to sqlite? Or postgres to mysql? Or mysql to postgres?</p>
<p>Recently I've been migrating my django project from MySQL to SQLite. I tried to use <a href="https://docs.djangoproject.com/en/1.9/ref/django-admin/#dumpdata">dumpdata</a> and <a href="https://docs.djangoproject.com/en/1.9/ref/django-admin/#django-admin-loaddata">loaddata</a> commands.</p>
<ul>
<li><code>dumpdata</code> can save your database to json file</li>
<li><code>loaddata</code> can populate your database</li></ul></div>]]></description><link>http://blog.chib.me/how-to-convert-databases-with-one-line-of-code/</link><guid isPermaLink="false">59f600d050b3c800013c3a40</guid><dc:creator><![CDATA[Gennady Chibisov]]></dc:creator><pubDate>Sun, 07 Feb 2016 13:03:06 GMT</pubDate><media:content url="http://blog.chib.me/content/images/2016/02/main_image-11.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="http://blog.chib.me/content/images/2016/02/main_image-11.png" alt="How to convert databases with one line of code"><p>Have you ever wanted to convert mysql database to sqlite? Or postgres to mysql? Or mysql to postgres?</p>
<p>Recently I've been migrating my django project from MySQL to SQLite. I tried to use <a href="https://docs.djangoproject.com/en/1.9/ref/django-admin/#dumpdata">dumpdata</a> and <a href="https://docs.djangoproject.com/en/1.9/ref/django-admin/#django-admin-loaddata">loaddata</a> commands.</p>
<ul>
<li><code>dumpdata</code> can save your database to json file</li>
<li><code>loaddata</code> can populate your database with entities from the json file</li>
</ul>
<p>But it is not so simple as it sounds:</p>
<ul>
<li>You must migrate your SQLite database first, because json file will not contain any schema altering instructions</li>
<li>You must remove any conflicting content from <code>contrib.auth</code> application. For example, permissions or initialized superusers</li>
<li>Even if you did all these steps you can have problems with indexes or duplicates</li>
</ul>
<p>I decided to move on and try to convert my databases directly (without the django facilities). I checked stackoverflow and found many scripts for converting your MySQL dump to SQLite dump. For example <a href="https://gist.github.com/esperlu/943776#file-mysql2sqlite-sh">mysql2sqlite.sh</a> (don't use it!). But it failed too.</p>
<p>When I was already despaired I found an awesome project called <a href="http://sequel.jeremyevans.net/">sequel</a>. It's ORM written in Ruby. Sequel has a command line interface and an option for <a href="http://sequel.jeremyevans.net/rdoc/files/doc/bin_sequel_rdoc.html#label-Copy+Databases">copying one database to another</a> even between different database types. Using <code>-C</code>  option, you provide two connection strings:</p>
<pre><code>sequel -C mysql://host1/database postgres://host2/database2
</code></pre>
<p>This copies the table structure, table data, indexes, and foreign keys from the MySQL database to the PostgreSQL database.</p>
<p>As I understand sequel performs 4 major steps for copying databases:</p>
<ul>
<li>It connects to the source database and extracts the metadata (table structure, table data, indexes, foreign keys)</li>
<li>Then it builds an ORM models based on the extracted metadata</li>
<li>Then it migrates a target database based on the ORM models</li>
<li>Finally it populates the target database with the data from the source database</li>
</ul>
<p>Sequel is awesome but for using it you have to install Ruby, gem package manager, sequel itself and all drivers you need for connecting to your databases. I found it inconvenient and have built a docker image called <a href="https://hub.docker.com/r/chibisov/sequel/">chibisov/sequel</a>. It packed with sequel + drivers for MySQL, Postgres and SQLite.</p>
<p>With just one line of code I can migrate MySQL database to SQLite file:</p>
<pre><code>$ docker run --name mysql2sqlite chibisov/sequel:4.31.0 sequel -C mysql2://user:password@host/database sqlite://db.sqlite
</code></pre>
<p><em>Be careful with the mysql connection string. You should use <code>mysql2://</code> adapter because the default <code>mysql://</code> adapter has problems with <code>utf-8</code> encoding.</em></p>
<p>That's it! Now I can copy the database file to my host machine and remove the intermediate container:</p>
<pre><code>$ docker cp mysql2sqlite:/db.sqlite ./
$ docker rm -v mysql2sqlite
</code></pre>
<p>You can watch how I convert databases in my terminal session:</p>
<style>.asciicast{text-align: center;}</style>
<script type="text/javascript" src="https://asciinema.org/a/689xjggzc3w7b31dlb7hgt8zv.js" id="asciicast-689xjggzc3w7b31dlb7hgt8zv" async></script>
</div>]]></content:encoded></item><item><title><![CDATA[What's wrong with Youtube's watch later list?]]></title><description><![CDATA[<div class="kg-card-markdown"><p>In <a href="https://docast.me/">Docast</a> we love to watch Youtube. Sometimes it's hard to find time to watch videos but thankfully Youtube has &quot;Watch Later&quot; button. Video will be added to the special playlist after you clicked it.</p>
<img src="http://blog.chib.me/content/images/2015/12/mac-1.png" style="max-height: 176px">
<p>Why is it so convenient? Because it's one or two clicks action button.</p></div>]]></description><link>http://blog.chib.me/whats-wrong-with-youtubes-watch-later-list/</link><guid isPermaLink="false">59f600d050b3c800013c3a3e</guid><category><![CDATA[docast]]></category><category><![CDATA[youtube]]></category><dc:creator><![CDATA[Gennady Chibisov]]></dc:creator><pubDate>Sat, 12 Dec 2015 16:32:40 GMT</pubDate><media:content url="http://blog.chib.me/content/images/2015/12/main-4.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="http://blog.chib.me/content/images/2015/12/main-4.png" alt="What's wrong with Youtube's watch later list?"><p>In <a href="https://docast.me/">Docast</a> we love to watch Youtube. Sometimes it's hard to find time to watch videos but thankfully Youtube has &quot;Watch Later&quot; button. Video will be added to the special playlist after you clicked it.</p>
<img src="http://blog.chib.me/content/images/2015/12/mac-1.png" style="max-height: 176px" alt="What's wrong with Youtube's watch later list?">
<p>Why is it so convenient? Because it's one or two clicks action button. On any mobile app it's the fastest way to save video for watching it later.</p>
<img src="http://blog.chib.me/content/images/2015/12/mobile_with_titles.png" style="max-height: 449px" alt="What's wrong with Youtube's watch later list?">
<h3 id="houstonwehaveaproblem">Houston we have a problem</h3>
<p>As I've described &quot;Watch Later&quot; button is the fastest way to save video to the special playlist. But it isn't the regular playlist. Look at the difference.</p>
<img src="http://blog.chib.me/content/images/2015/12/comparison.png" style="max-height: 503px" alt="What's wrong with Youtube's watch later list?">
<p>With &quot;Watch Later&quot; playlist you are <strong>not</strong> able to:</p>
<ul>
<li>Share it</li>
<li>Change settings. E.g. change video ordering</li>
</ul>
<p>With normal playlist you can set ordering by date added (newest first).<br>
<img src="http://blog.chib.me/content/images/2015/12/screen_playlist_settings-1.png" style="max-height: 321px" alt="What's wrong with Youtube's watch later list?"></p>
<p>And here we come to the biggest problem of the default &quot;Watch Later&quot; playlist - <strong>you cannot change the default ordering of the videos which is Date added (oldest)</strong>. When you add a new video to the playlist <strong>it will be added to the end of the list</strong>. You'll have to scroll down to see the latest items.</p>
<img src="http://blog.chib.me/content/images/2015/12/load_more-2.png" style="max-height: 68px" alt="What's wrong with Youtube's watch later list?">
<h3 id="thesolution">The solution</h3>
<p>We've built a service which solves the problem. The website is <a href="https://watchlater.chib.me">https://watchlater.chib.me</a>. I'll explain how it works:</p>
<ul>
<li>Ensure that you have a youtube channel. To create a youtube channel <a href="https://www.youtube.com/create_channel">use this link</a>.</li>
<li>Click &quot;Start using&quot;</li>
<li>Allow to our service to work with your youtube account</li>
<li>After that we will create a normal playlist named &quot;Watch Later Service&quot;</li>
<li>Every five minutes we will move all your videos from default &quot;Watch Later&quot; list to the normal playlist.</li>
<li>That's it 😊</li>
</ul>
<p>Why is it cool? Because you don't have to move videos manually and you will be able to use convenient &quot;Watch Later&quot; button. Enjoy!</p>
</div>]]></content:encoded></item><item><title><![CDATA[How to watch football highlights on any device?]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I love football but I don't have enough time to watch all matches. With <a href="https://docast.me">Docast.me</a> I can watch the latest football highlights anytime from many special websites. For example <a href="http://www.timesoccer.com/">timesoccer.com</a>.</p>
<p><img src="http://blog.chib.me/content/images/2015/11/Football_Highlights.png" alt=""></p>
<p>Such sites have all recent football matches but they are inconvenient in many ways:</p>
<ul>
<li>Videos with highlights could</li></ul></div>]]></description><link>http://blog.chib.me/how-to-watch-football-highlights-on-any-device/</link><guid isPermaLink="false">59f600d050b3c800013c3a3c</guid><category><![CDATA[docast]]></category><category><![CDATA[podcast]]></category><category><![CDATA[football highlights]]></category><category><![CDATA[offline]]></category><category><![CDATA[rss]]></category><dc:creator><![CDATA[Gennady Chibisov]]></dc:creator><pubDate>Sun, 22 Nov 2015 16:33:32 GMT</pubDate><media:content url="http://blog.chib.me/content/images/2015/11/barca_2-1.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="http://blog.chib.me/content/images/2015/11/barca_2-1.jpg" alt="How to watch football highlights on any device?"><p>I love football but I don't have enough time to watch all matches. With <a href="https://docast.me">Docast.me</a> I can watch the latest football highlights anytime from many special websites. For example <a href="http://www.timesoccer.com/">timesoccer.com</a>.</p>
<p><img src="http://blog.chib.me/content/images/2015/11/Football_Highlights.png" alt="How to watch football highlights on any device?"></p>
<p>Such sites have all recent football matches but they are inconvenient in many ways:</p>
<ul>
<li>Videos with highlights could have ads. I don't want to waste time on it.</li>
<li>There are no notifications about new games.</li>
<li>There are no convenient controles (skipping 30 forward, increasing speed, etc...)</li>
<li>There is no way I could watch videos in offline mode (e.g. in the underground).</li>
</ul>
<p>You know what - there is an app with the all described features. It’s already on your device. It’s called the podcasting app!</p>
<img src="http://blog.chib.me/content/images/2015/11/lg_leon_football-2.jpg" width="200" alt="How to watch football highlights on any device?">
<h3 id="rssdocast">RSS + Docast = ♥</h3>
<p>We've added RSS integration to <a href="https://docast.me/">Docast</a> recently. It opens possibilities for listening text articles and watch videos from any webpage. Actually it works the same way as the <a href="https://blog.chib.me/why-we-put-the-internet-into-your-favorit-podcasting-app/#pocket">Pocket integration</a>:</p>
<iframe width="420" height="315" src="https://www.youtube.com/embed/sj7RukqiYr8?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>
<p>The difference is:</p>
<ul>
<li>With the Pocket you subscribe to your personal pocket bookmarks.</li>
<li>With the RSS you subscribe to opened RSS feed.</li>
</ul>
<p>There are just two steps you should perform for watching football highlights in any podcasting app:</p>
<ul>
<li>Create a podcast from your favorite website's RSS.</li>
<li>Add the podcast link to your app as it described in the video above.</li>
</ul>
<p>I'll give you a <a href="https://docast.me/app/services/feed/?url=http%3A%2F%2Fwww.timesoccer.com%2Ffeed">short link</a> for the timesoccer.com. Just follow it and click &quot;Create podcast&quot; button:</p>
<p><img src="http://blog.chib.me/content/images/2015/11/click_feed.png" alt="How to watch football highlights on any device?"></p>
<p>Now you can enjoy football highlights right in your favorite podcasting app! On any device. Without ads. With notifications. With convenient speed controles. In offline mode!</p>
<img src="http://blog.chib.me/content/images/2015/11/football_podcast.jpg" width="200" alt="How to watch football highlights on any device?">
<h3 id="ps">P.S.</h3>
<p>I've been using <code>timesoccer.com</code> feed for a while and found it inconvenient. It's slow updating and sometimes they mix text articles into their feed.<br>
I found another great website <a href="http://footyroom.com/">footyroom.com</a> but they don't have RSS feed I could use.</p>
<p>Thankfully as a developer I can build my own RSS feed. I wrote little script with <code>GO</code> which parses  <code>footyroom.com</code>. Resulting feed you can find <a href="https://highlights.chib.me/">here</a>. You can start watching highlights from the feed using Docast. Just follow a <a href="https://docast.me/app/services/feed/?url=https%3A%2F%2Fhighlights.chib.me%2F">short link</a> and click &quot;Create podcast&quot; button.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Why we put the Internet into your favorite podcasting app]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I love to listen and I’m not alone. There are so many people who love to listen music, radio, podcasts, audiobooks. Recently we’ve built a small service for integrating  different data sources into your favorite podcasting app, which is the most convenient app for listening audios and watching</p></div>]]></description><link>http://blog.chib.me/why-we-put-the-internet-into-your-favorit-podcasting-app/</link><guid isPermaLink="false">59f600d050b3c800013c3a3b</guid><category><![CDATA[docast]]></category><category><![CDATA[podcast]]></category><category><![CDATA[dropbox]]></category><category><![CDATA[pocket]]></category><category><![CDATA[youtube]]></category><dc:creator><![CDATA[Gennady Chibisov]]></dc:creator><pubDate>Sun, 15 Nov 2015 13:45:00 GMT</pubDate><media:content url="http://blog.chib.me/content/images/2015/11/main-1.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="http://blog.chib.me/content/images/2015/11/main-1.jpg" alt="Why we put the Internet into your favorite podcasting app"><p>I love to listen and I’m not alone. There are so many people who love to listen music, radio, podcasts, audiobooks. Recently we’ve built a small service for integrating  different data sources into your favorite podcasting app, which is the most convenient app for listening audios and watching video content. The service could be found here <a href="https://docast.me">https://docast.me</a>.</p>
<p>In general, <a href="https://docast.me">Docast</a> builds a podcasting feed from your Dropbox, Youtube, Pocket and other services accounts. For futher details keep reading.</p>
<h3 id="audiobooks">Audiobooks</h3>
<p>Recently I’ve started listening historic and fiction audiobooks. But I was surprised that iTunes is not capable to play them in a simple mp3 format. I have to use third party apps to import them to the iPhone. But what if I could just place my folder with mp3 files to the Dropbox and start listening audiobooks?</p>
<p>Special apps for audiobooks is another problem. They all are out of date or wants you to use their cloud (e.g. <a href="https://play.google.com/store/apps/details?id=com.audible.application">audible</a>). Try to find these apps for Windows Phone.</p>
<p>I want more! I want to change speed, remove silence, boost voice (especially when I’m in the subway).</p>
<p>To sum up. App for reading audiobooks should:</p>
<ul>
<li>Be capable to play simple mp3 files from simple folders</li>
<li>Have audio filters</li>
<li>Track my listen history</li>
<li>Be integrated with Dropbox</li>
</ul>
<p>There is an app with the all (except latest) described features. It’s already on your device and it’s called the podcasting app!</p>
<p>Watch short video how you can listen audiobooks from the Dropbox in <a href="https://overcast.fm/">Overcast</a>:</p>
<iframe width="420" height="315" src="https://www.youtube.com/embed/ElWxfmDbaBA?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>
<h3 id="youtube">Youtube</h3>
<p>I like to watch interesting videos from Youtube. Most of them I want to listen. I don’t need to see an image from <a href="https://www.youtube.com/watch?v=iMjy0FV8i_I">Antony C. Sutton's</a> interview. I just want to listen, that’s it! But Youtube app on my iPhone has a big problem - it stops playing audio when I lock my phone!</p>
<p>Let’s talk about an offline mode. I use a public transport to commute to my work. And it’s horrible to watch youtube with 3G, LTE or even with WI-FI in an underground. Yeah, I know about <a href="https://www.youtube.com/red">Youtube Red</a>, but:</p>
<ul>
<li>It wants 10$ a month</li>
<li>It still has problems with stopping audio when phone is locked</li>
<li>It isn’t available in my country =(</li>
</ul>
<p><img src="http://blog.chib.me/content/images/2015/11/YouTube-2015-11-15-12-29-59.jpg" alt="Why we put the Internet into your favorite podcasting app"></p>
<p>Another feature I need is a synchronization. I want to start listening or watching process on iPhone, continue with iPad and finish with my Mac. Moreover I use an Android device! And I want to work it there to. Thankfully there is an app for that. It is called <a href="http://www.shiftyjelly.com/pocketcasts">Pocket Casts</a>. To watch videos in offline you just have to enable auto download. It keeps in sync your watch progress across the all devices. It’s cross-platform. Did you know that Pocket Casts is <a href="http://blog.chib.me/why-we-put-the-internet-into-your-favorit-podcasting-app/(https://twitter.com/pocketcasts/status/578695016224190464)">available in your car or chromecast</a>?</p>
<p>Look at a short video how you can watch videos from your Youtube’s &quot;Watch later&quot; playlist:</p>
<iframe width="420" height="315" src="https://www.youtube.com/embed/mtZWL5NaiGw?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>
<p>By the way, if you use only Apple devices then it keeps your episodes in sync out of the box.</p>
<h3 id="pocket">Pocket</h3>
<p>I like to read articles from the <a href="http://techcrunch.com/">techcrunch</a>. If want to read some publication I’ll add it to my <a href="https://getpocket.com">Pocket</a>. But after a day in front of my computer I want to listen it, not to read. Yeah, I know about <a href="https://getpocket.com/blog/2012/09/available-now-text-to-speech-in-pockets-new-listen-feature-for-android/">text to speech</a> integrated into Pocket, but it’s inconvenient in many ways:</p>
<ul>
<li>When you lock your phone it will hide most of the controle buttons</li>
<li>Sometimes it flushes all your listening history</li>
<li>There are no audio effects mentioned above</li>
<li>It works only on iOS and Android devices</li>
</ul>
<p>With <a href="https://docast.me">Docast</a> you won’t have these problems. All you need is just to connect it with your Pocket account and subscribe to podcast.</p>
<p>Look at a short video how you can listen text articles from any page in the Internet. Moreover, <a href="https://docast.me">Docast</a> can extract audio and video content from any page. It’s compatible with more than 500 services (e.g. Youtube, Vimeo, Soundcloud, etc…):</p>
<iframe width="420" height="315" src="https://www.youtube.com/embed/sj7RukqiYr8?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>
<h3 id="tosumup">To sum up</h3>
<p>I’ve been consuming all my media content in a podcasting app for more than 3 months by now. I’ve been listening articles from Pocket. I’ve been listening audiobooks from Dropbox. I’ve been watching videos from Youtube. On any device. It’s all in sync. In offline mode. Magic!</p>
<p>One day I came to a conclusion that podcasting app is the most convenient media center app with opened capabilities for integration. Till now there was no simple way to integrate your personal content into this media center. That’s why we’ve built <a href="https://docast.me">Docast</a>.</p>
</div>]]></content:encoded></item></channel></rss>