Transfer Encoding chunked with okhttp only delivers full result - retrofit

I am trying to get some insights on a chunked endpoint and therefore planned to print what the server sends me chunk by chunk. I failed to do so so I wrote a test to see if OkHttp/Retrofit are working as I expect it.
The following test should deliver some chunks to the console but all I get is the full response.
I am a bit lost what I am missing to even make the MockWebServer of OkHttp3 sending me chunks.
I found this retrofit issue entry but the answer is a bit ambiguous for me: Chunked Transfer Encoding Response
class ChunkTest {
#Rule
#JvmField
val rule = RxImmediateSchedulerRule() // custom rule to run Rx synchronously
#Test
fun `test Chunked Response`() {
val mockWebServer = MockWebServer()
mockWebServer.enqueue(getMockChunkedResponse())
val retrofit = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(OkHttpClient.Builder().build())
.build()
val chunkedApi = retrofit.create(ChunkedApi::class.java)
chunkedApi.getChunked()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
System.out.println(it.string())
}, {
System.out.println(it.message)
})
mockWebServer.shutdown()
}
private fun getMockChunkedResponse(): MockResponse {
val mockResponse = MockResponse()
mockResponse.setHeader("Transfer-Encoding", "chunked")
mockResponse.setChunkedBody("THIS IS A CHUNKED RESPONSE!", 5)
return mockResponse
}
}
interface ChunkedApi {
#Streaming
#GET("/")
fun getChunked(): Flowable<ResponseBody>
}
Test console output:
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 execute
INFO: MockWebServer[49293] starting to accept connections
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$3 processOneRequest
INFO: MockWebServer[49293] received request: GET / HTTP/1.1 and responded: HTTP/1.1 200 OK
THIS IS A CHUNKED RESPONSE!
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 acceptConnections
INFO: MockWebServer[49293] done accepting connections: Socket closed
I expected to be more like (body "cut" every 5 bytes):
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 execute
INFO: MockWebServer[49293] starting to accept connections
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$3 processOneRequest
INFO: MockWebServer[49293] received request: GET / HTTP/1.1 and responded: HTTP/1.1 200 OK
THIS
IS A
CHUNKE
D RESPO
NSE!
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 acceptConnections
INFO: MockWebServer[49293] done accepting connections: Socket closed

The OkHttp Mockserver does chunk the data, however it looks like the LoggingInterceptor waits until the whole chunks buffer is full then it displays it.
From this nice summary about HTTP streaming:
The use of Transfer-Encoding: chunked is what allows streaming within a single request or response. This means that the data is transmitted in a chunked manner, and does not impact the representation of the content.
With that in mind, we are dealing with 1 "request / response", which means we'll have to do our chunks retrieval before getting the entire response. Then pushing each chunk in our own buffer, all that on an OkHttp network interceptor.
Here is an example of said NetworkInterceptor:
class ChunksInterceptor: Interceptor {
val Utf8Charset = Charset.forName ("UTF-8")
override fun intercept (chain: Interceptor.Chain): Response {
val originalResponse = chain.proceed (chain.request ())
val responseBody = originalResponse.body ()
val source = responseBody!!.source ()
val buffer = Buffer () // We create our own Buffer
// Returns true if there are no more bytes in this source
while (!source.exhausted ()) {
val readBytes = source.read (buffer, Long.MAX_VALUE) // We read the whole buffer
val data = buffer.readString (Utf8Charset)
println ("Read: $readBytes bytes")
println ("Content: \n $data \n")
}
return originalResponse
}
}
Then of course we register this Network Interceptor on the OkHttp client.

Related

Play Framework running Asynchronous Web Calls times out

I have to perform three HTTP requests from a Play Application. These calls are directed towards three subprojects of the main application. The architecture looks like this:
main app
|
--modules
|
--component1
|
--component2
|
--component3
each component* is an individual sbt subprojects.
The main class in main app runs this Action:
def service = Action.async(BodyParsers.parse.json) { implicit request =>
val query = request.body
val url1 = "http://localhost:9000/one"
val url2 = "http://localhost:9000/two"
val url3 = "http://localhost:9000/three"
val sync_calls = for {
a <- ws.url(url1).withRequestTimeout(Duration.Inf).withHeaders("Content-Type"->"application/json")
.withBody(query).get()
b <- ws.url(url2).withRequestTimeout(Duration.Inf).withHeaders("Content-Type"->"application/json")
.withBody(a.body).get()
c <- ws.url(url3).withRequestTimeout(Duration.Inf).withHeaders("Content-Type"->"application/json")
.withBody(b.body).get()
} yield c.body
sync_calls.map(x => Ok(x))
}
The components need to be activated one after the other, so they need to be a sequence. Each of the does a spark job. However when I call the service action I get this error:
[error] application -
! #71og96ol6 - Internal server error, for (GET) [/automatic] ->
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[TimeoutException: Read timeout to localhost/127.0.0.1:9000 after 120000 ms]] [TimeoutException: Read timeout to localhost/127.0.0.1:9000 after 120000 ms]
at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:280)
at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:206)
at play.api.GlobalSettings$class.onError(GlobalSettings.scala:160)
at play.api.DefaultGlobal$.onError(GlobalSettings.scala:188)
at play.api.http.GlobalSettingsHttpErrorHandler.onServerError(HttpErrorHandler.scala:98) │
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:1│
00)
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:9│
9)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:346)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:345)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
Caused by: java.util.concurrent.TimeoutException: Read timeout to localhost/127.0.0.1:9000 after 120000 ms
at org.asynchttpclient.netty.timeout.TimeoutTimerTask.expire(TimeoutTimerTask.java:43)
at org.asynchttpclient.netty.timeout.ReadTimeoutTimerTask.run(ReadTimeoutTimerTask.java:54)
at io.netty.util.HashedWheelTimer$HashedWheelTimeout.expire(HashedWheelTimer.java:581)
at io.netty.util.HashedWheelTimer$HashedWheelBucket.expireTimeouts(HashedWheelTimer.java:655)
at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:367)
at java.lang.Thread.run(Thread.java:745)
and
HTTP/1.1 500 Internal Server Error
Content-Length: 4931
Content-Type: text/html; charset=utf-8
Date: Tue, 25 Oct 2016 21:06:17 GMT
<body id="play-error-page">
<p id="detail" class="pre">[TimeoutException: Read timeout to localhost/127.0.0.1:9000 after 120000 ms]</p>
</body>
I specifically set the Timeout for each call to Duration.Inf for the purpose of avoiding timouts. Why is this happening and how do I fix it?

Why is do_GET much faster than do_POST

Python's BaseHTTPRequestHandler has an issue with forms sent through post!
I have seen other people asking the same question (Why GET method is faster than POST?), but the time difference in my case is too much (1 second)
Python server:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import datetime
def get_ms_since_start(start=False):
global start_ms
cur_time = datetime.datetime.now()
# I made sure to stay within hour boundaries while making requests
ms = cur_time.minute*60000 + cur_time.second*1000 + int(cur_time.microsecond/1000)
if start:
start_ms = ms
return 0
else:
return ms - start_ms
class MyServer(BaseHTTPRequestHandler, object):
def do_GET(self):
print "Start get method at %d ms" % get_ms_since_start(True)
field_data = self.path
self.send_response(200)
self.end_headers()
self.wfile.write(str(field_data))
print "Sent response at %d ms" % get_ms_since_start()
return
def do_POST(self):
print "Start post method at %d ms" % get_ms_since_start(True)
length = int(self.headers.getheader('content-length'))
print "Length to read is %d at %d ms" % (length, get_ms_since_start())
field_data = self.rfile.read(length)
print "Reading rfile completed at %d ms" % get_ms_since_start()
self.send_response(200)
self.end_headers()
self.wfile.write(str(field_data))
print "Sent response at %d ms" % get_ms_since_start()
return
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8082), MyServer)
print 'Starting server, use <Ctrl-C> to stop'
server.serve_forever()
Get request with python server is very fast
time curl -i http://0.0.0.0:8082\?one\=1
prints
HTTP/1.0 200 OK
Server: BaseHTTP/0.3 Python/2.7.6
Date: Sun, 18 Sep 2016 07:13:47 GMT
/?one=1curl http://0.0.0.0:8082\?one\=1 0.00s user 0.00s system 45% cpu 0.012 total
and on the server side:
Start get method at 0 ms
127.0.0.1 - - [18/Sep/2016 00:26:30] "GET /?one=1 HTTP/1.1" 200 -
Sent response at 0 ms
Instantaneous!
Post request when sending form to python server is very slow
time curl http://0.0.0.0:8082 -F one=1
prints
--------------------------2b10061ae9d79733
Content-Disposition: form-data; name="one"
1
--------------------------2b10061ae9d79733--
curl http://0.0.0.0:8082 -F one=1 0.00s user 0.00s system 0% cpu 1.015 total
and on the server side:
Start post method at 0 ms
Length to read is 139 at 0 ms
Reading rfile completed at 1002 ms
127.0.0.1 - - [18/Sep/2016 00:27:16] "POST / HTTP/1.1" 200 -
Sent response at 1002 ms
Specifically, self.rfile.read(length) is taking 1 second for very little form data
Post request when sending data (not form) to python server is very fast
time curl -i http://0.0.0.0:8082 -d one=1
prints
HTTP/1.0 200 OK
Server: BaseHTTP/0.3 Python/2.7.6
Date: Sun, 18 Sep 2016 09:09:25 GMT
one=1curl -i http://0.0.0.0:8082 -d one=1 0.00s user 0.00s system 32% cpu 0.022 total
and on the server side:
Start post method at 0
Length to read is 5 at 0
Reading rfile completed at 0
127.0.0.1 - - [18/Sep/2016 02:10:18] "POST / HTTP/1.1" 200 -
Sent response at 0
node.js server:
var http = require('http');
var qs = require('querystring');
http.createServer(function(req, res) {
if (req.method == 'POST') {
whole = ''
req.on('data', function(chunk) {
whole += chunk.toString()
})
req.on('end', function() {
console.log(whole)
res.writeHead(200, 'OK', {'Content-Type': 'text/html'})
res.end('Data received.')
})
}
}).listen(8082)
Post request when sending form to node.js server is very fast
time curl -i http://0.0.0.0:8082 -F one=1
prints:
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Content-Type: text/html
Date: Sun, 18 Sep 2016 10:31:38 GMT
Connection: keep-alive
Transfer-Encoding: chunked
Data received.curl -i http://0.0.0.0:8082 -F one=1 0.00s user 0.00s system 42% cpu 0.013 total
I think this is the answer to your problem: libcurl delays for 1 second before uploading data, command-line curl does not
libcurl is sending the Expect 100-Continue header, and waiting 1 second for a response before sending the form data (in the case of the -F command).
In the case of -d, it does not send the 100-Continue header, for whatever reason.

Why is the server sending an http 200 response instead of expected 206?

History:
We are delivering .mp4 video via http partial request streaming. It works fine except on iPad/iPhone and we have followed all of the recommended procedures for encoding .mp4 files for iOS devices so the issue is not the files themselves. The video files are stored outside of the root of the web server so we have written code to open and stream the video upon request. Our testing has brought us to the point of comparing the headers of our "streamed" video (working but not in IOS) and linking directly to the file (working in all instances but not a possible solution as we need to store files outside of web root). Here is a comparison of the working vs. non-working headers.
link direct to file (working for IOS):
request 1
Status Code 200 OK
Accept-Ranges:bytes
Content-Length:62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 08:11:15 GMT
ETag:"60be3570ae7ed01:0"
Last-Modified:Fri, 24 Apr 2015 16:48:06 GMT
Server:Microsoft-IIS/7.0
X-Powered-By:ASP.NET
request 2
Status Code:206 Partial Content
Accept-Ranges:bytes
Content-Length:62086289
Content-Range:bytes 0-62086288/62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 08:11:16 GMT
ETag:"60be3570ae7ed01:0"
Last-Modified:Fri, 24 Apr 2015 16:48:06 GMT
Server:Microsoft-IIS/7.0
X-Powered-By:ASP.NET
opening file for streaming (not working for IOS)
request 1
Status Code 200 OK
Accept-Ranges:bytes
Cache-Control:private
Content-Length:62086289
Content-Range:bytes 0-62086289/62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 16:55:22 GMT
Server:Microsoft-IIS/7.0
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
request 2
Status Code 200 OK
Accept-Ranges:bytes
Cache-Control:private
Content-Length:62086289
Content-Range:bytes 0-62086289/62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 16:55:22 GMT
Server:Microsoft-IIS/7.0
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
Notice that the non working version's second request stays at Status Code 200 and does not change to 206. This is what we are focusing on as possibly being the issue but are at a loss. Below is the code to open the file and send to server. Note the way we are setting up the header.
Dim iStream As System.IO.Stream
' Buffer to read 10K bytes in chunk:
Dim buffer(10000) As Byte
' Length of the file:
Dim length As Integer=0
' Total bytes to read:
Dim dataToRead As Long=0
Dim isPartialFile As Boolean = False
Dim totalDelivered As Long = 0
Dim totalFileSize As Long = 0
Dim filename As String
filename = fileID & file.Extension
iStream = New System.IO.FileStream(filePath, System.IO.FileMode.Open,IO.FileAccess.Read, IO.FileShare.Read)
' Total bytes to read:
dataToRead = iStream.Length
totalFileSize = iStream.Length
Response.ContentType = "video/mp4"
Response.AddHeader("Accept-Ranges", "bytes")
Response.AddHeader("Content-Range", "bytes 0-" + totalFileSize.ToString + "/" + totalFileSize.ToString)
Response.AddHeader("Content-Length", totalFileSize.ToString)
' Read the bytes.
While dataToRead > 0
' Verify that the client is connected.
If Response.IsClientConnected Then
' Read the data in buffer
length = iStream.Read(buffer, 0, 10000)
' Write the data to the current output stream.
Response.OutputStream.Write(buffer, 0, length)
' Flush the data to the HTML output.
Response.Flush()
ReDim buffer(10000) ' Clear the buffer
dataToRead = dataToRead - length
Else
isPartialFile = True
'record the data read in the database HERE (partial file)
'totalDelivered = totalFileSize - dataToRead
'prevent infinite loop if user disconnects
dataToRead = -1
End If
End While
iStream.Close()
Response.End()
How do we modify the above code to make it work with IOS devices assuming that the issue is with the 206 response header?

Sieve not filtering email

I setup Postfix with Spamassassin and Dovecot with Sieve. Spamassassin will tag the email as spam.
I am trying to get Sieve to move mail tagged as "Spam" by SpamAssassin into the Junk folder. However, it is not doing so. I have no idea what I am doing wrong and am quite frustrated.
Here's my dovecot.conf dump:
$ dovecot -n
# 2.2.9: /etc/dovecot/dovecot.conf
# OS: Linux 3.13.0-37-generic x86_64 Linux Mint 17.1 Rebecca ext4
info_log_path = /var/log/dovecot-info.log
log_path = /var/log/dovecot.log
mail_location = maildir:/var/mail/vmail/%u
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave
namespace {
inbox = yes
location =
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
prefix =
separator = /
type = private
}
passdb {
args = scheme=plain /etc/dovecot/passwd
driver = passwd-file
}
plugin {
sieve = ~/.dovecot.sieve
sieve_before = /etc/dovecot/sieve/sieve.default
sieve_default = /etc/dovecot/sieve/sieve.default
sieve_dir = ~/sieve
sieve_global_dir = /var/lib/dovecot/sieve/
}
protocols = imap lmtp sieve
service auth {
unix_listener /var/spool/postfix/private/auth {
group = postfix
user = postfix
}
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
inet_listener sieve_deprecated {
port = 2000
}
process_min_avail = 0
service_count = 1
vsz_limit = 64 M
}
service managesieve {
process_limit = 1024
}
ssl_cert = </etc/ssl/certs/ssl-cert-snakeoil.pem
ssl_key = </etc/ssl/private/ssl-cert-snakeoil.key
userdb {
args = uid=vmail gid=vmail home=/home/vmail/%u
driver = static
}
protocol lda {
mail_plugins = " sieve"
}
protocol lmtp {
mail_plugins = " sieve"
}
protocol sieve {
mail_max_userip_connections = 10
mail_plugins =
managesieve_implementation_string = Dovecot Pigeonhole
managesieve_logout_format = bytes=%i/%o
managesieve_max_compile_errors = 5
managesieve_max_line_length = 65536
managesieve_notify_capability =
managesieve_sieve_capability =
}
My /etc/dovecot/sieve/sieve.default is the following:
$ cat /etc/dovecot/sieve/sieve.default
require "fileinto";
if header :contains "X-Spam-Flag" "YES" {
fileinto "Junk";
}
The "Junk" folder exists. Here is a dump of the email:
From user#domain.tld Mon May 11 14:37:44 2015
Return-Path: <user#domain.tld>
X-Original-To: user#testmail.domain.tld
Delivered-To: user#testmail.domain.tld
Received: by Linux-Mint (Postfix, from userid 5001)
id A59ECE34D1; Mon, 11 May 2015 14:37:44 -0400 (EDT)
Received: from localhost by Linux-Mint
with SpamAssassin (version 3.4.0);
Mon, 11 May 2015 14:37:44 -0400
From: User <user#domain.tld>
To: user#testmail.domain.tld
Subject: *****SPAM***** This is Junk Mail
Date: Mon, 11 May 2015 14:37:38 -0400
Message-Id: <20150511183738.GD7930#user-pc>
X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on Linux-Mint
X-Spam-Flag: YES
X-Spam-Level: **************************************************
X-Spam-Status: Yes, score=1000.0 required=5.0 tests=GTUBE autolearn=no
autolearn_force=no version=3.4.0
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----------=_5550F6F8.2DF3E67D"
Status: RO
Content-Length: 1867
Lines: 52
This is a multi-part message in MIME format.
------------=_5550F6F8.2DF3E67D
Content-Type: text/plain; charset=iso-8859-1
Content-Disposition: inline
Content-Transfer-Encoding: 8bit
Spam detection software, running on the system "Linux-Mint",
has identified this incoming email as possible spam. The original
message has been attached to this so you can view it or label
similar future email. If you have any questions, see
##CONTACT_ADDRESS## for details.
Content preview: GTUBE string: XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
[...]
Content analysis details: (1000.0 points, 5.0 required)
pts rule name description
---- ---------------------- --------------------------------------------------
1000 GTUBE BODY: Generic Test for Unsolicited Bulk Email
------------=_5550F6F8.2DF3E67D
Content-Type: message/rfc822; x-spam-type=original
Content-Description: original message before SpamAssassin
Content-Disposition: inline
Content-Transfer-Encoding: 8bit
Received: from domain.tld (my-hostname.domain.tld [IPv6:REMOVED])
by Linux-Mint (Postfix) with ESMTPS id 0A6A2E34BF
for <user#testmail.domain.tld>; Mon, 11 May 2015 14:37:40 -0400 (EDT)
Received: from user-pc (unknown [IPv6:2001:470:8:209::c0ff:ee])
by domain.tld (Postfix) with ESMTPSA id 03FD41028E
for <user#testmail.domain.tld>; Mon, 11 May 2015 18:37:40 +0000 (UTC)
Date: Mon, 11 May 2015 14:37:38 -0400
From: User <user#domain.tld>
To: user#testmail.domain.tld
Subject: This is Junk Mail
Message-ID: <20150511183738.GD7930#user-pc>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.21 (2010-09-15)
GTUBE string:
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
------------=_5550F6F8.2DF3E67D--
I am testing this email from one server to another, the sender and receiver are not the same machine. The recipient was user#testmail.domain.tld and the sender was user#domain.tld. The email is clearly marked as spam.
Why isn't it moving the email into the Junk folder?
Figured it out. As I am still a noob, my answer may not be the best, but I hope it helps someone out there.
This had a few problems, but primarily postfix was not routing mail to dovecot for delivery. Dovecot+sieve were totally bypassed.
To fix this, I had to have Postfix and Dovecot communicate over lmtp. First, I setup dovecot:
protocol lda {
mail_plugins = $mail_plugins sieve
}
protocol lmtp {
mail_plugins = $mail_plugins sieve
}
service lmtp {
inet_listener lmtp {
address = 127.0.0.1 ::1
port = 10025
}
}
Then I had to tell postfix to talk to dovecot. I did this by doing:
virtual_transport = lmtp:[::1]:10025
Then back to dovecot, I had to properly configure sieve.
plugin {
sieve = ~/.dovecot.sieve
sieve_dir = ~/sieve
sieve_before = /etc/dovecot/sieve/sieve.default
sieve_default = /etc/dovecot/sieve/sieve.default
sieve_global_dir = /var/lib/dovecot/sieve/
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
service_count = 1
process_min_avail = 0
vsz_limit = 64M
}
service managesieve {
process_limit = 1024
}
protocol sieve {
managesieve_max_line_length = 65536
mail_max_userip_connections = 10
# Space separated list of plugins to load (none known to be useful so far).
# Do NOT try to load IMAP plugins here.
mail_plugins =
managesieve_logout_format = bytes=%i/%o
managesieve_implementation_string = Dovecot Pigeonhole
managesieve_sieve_capability =
managesieve_notify_capability =
managesieve_max_compile_errors = 5
}
From here I put in the standard spamassassin filter rules in /etc/dovecot/sieve/sieve.default, which in my case was:
require "fileinto";
if header :contains "X-Spam-Flag" "YES" {
fileinto "Junk";
}
After that, I ran:
sievec /etc/dovecot/sieve/sieve.default
This "compiled" the sieve rules. And finally, I restarted dovecot and postfix.
sudo service dovecot restart
sudo service postfix restart
Upon that, I sent myself a test spam and it was redirected to the spam folder. I hope this helps and I ask that you pardon and correct any mistakes I may have made.

How to parse http headers in Go

I have http response headers shipped in logs from elsewhere. In my log file I have things like :-
Date: Fri, 21 Mar 2014 06:45:15 GMT\r\nContent-Encoding: gzip\r\nLast-Modified: Tue, 20 Aug 2013 15:45:41 GMT\r\nServer: nginx/0.8.54\r\nAge: 18884\r\nVary: Accept-Encoding\r\nContent-Type: text/html\r\nCache-Control: max-age=864000, public\r\nX-UA-Compatible: IE=Edge,chrome=1\r\nTiming-Allow-Origin: *\r\nContent-Length: 14888\r\nExpires: Mon, 31 Mar 2014 06:45:15 GMT\r\n
Given the above as string, how go I parse it into Header object as described in net/http . One way would be to split the string myself and map the key, values... But I am looking to avoid doing that by hand and use the standard (or well maintained 3rd party) library to parse it... Any pointers?
The builtin parser is in textproto. You can either use this directly, or add a
fake HTTP request header and use ReadRequest in the http package. Either way
you need to wrap your data into a bufio.Reader, here I'm just assuming we're
starting with a string.
With textproto:
logEntry := "Content-Encoding: gzip\r\nLast-Modified: Tue, 20 Aug 2013 15:45:41 GMT\r\nServer: nginx/0.8.54\r\nAge: 18884\r\nVary: Accept-Encoding\r\nContent-Type: text/html\r\nCache-Control: max-age=864000, public\r\nX-UA-Compatible: IE=Edge,chrome=1\r\nTiming-Allow-Origin: *\r\nContent-Length: 14888\r\nExpires: Mon, 31 Mar 2014 06:45:15 GMT\r\n"
// don't forget to make certain the headers end with a second "\r\n"
reader := bufio.NewReader(strings.NewReader(logEntry + "\r\n"))
tp := textproto.NewReader(reader)
mimeHeader, err := tp.ReadMIMEHeader()
if err != nil {
log.Fatal(err)
}
// http.Header and textproto.MIMEHeader are both just a map[string][]string
httpHeader := http.Header(mimeHeader)
log.Println(httpHeader)
and with http.ReadRequest:
logEntry := "Content-Encoding: gzip\r\nLast-Modified: Tue, 20 Aug 2013 15:45:41 GMT\r\nServer: nginx/0.8.54\r\nAge: 18884\r\nVary: Accept-Encoding\r\nContent-Type: text/html\r\nCache-Control: max-age=864000, public\r\nX-UA-Compatible: IE=Edge,chrome=1\r\nTiming-Allow-Origin: *\r\nContent-Length: 14888\r\nExpires: Mon, 31 Mar 2014 06:45:15 GMT\r\n"
// we need to make sure to add a fake HTTP header here to make a valid request.
reader := bufio.NewReader(strings.NewReader("GET / HTTP/1.1\r\n" + logEntry + "\r\n"))
logReq, err := http.ReadRequest(reader)
if err != nil {
log.Fatal(err)
}
log.Println(logReq.Header)
https://golang.org/pkg/net/textproto

Resources