Not to say anything silly like "Nim needs more published examples". :)
I'm writing an SSL client and and having some difficulties. Rather than posting the complex version, I've written a simplified example of a client that simply sends a string to a website in the sky:
import net
var ctx = newContext()
var next_socket = newSocket()
wrapSocket(ctx, next_socket)
next_socket.connect("www.google.com", Port(443), 1000)
next_socket.send("GET /")
I'm not really expecting the Google to honor my half-baked anonymous client. But I'm not expecting it to crash on send with:
/home/johnd/Projects/testyaml/testssl.nim(9) testssl
/home/johnd/.choosenim/toolchains/nim-1.2.6/lib/pure/net.nim(1468) send
/home/johnd/.choosenim/toolchains/nim-1.2.6/lib/pure/net.nim(834) socketError
/home/johnd/.choosenim/toolchains/nim-1.2.6/lib/pure/net.nim(481) raiseSSLError
Error: unhandled exception: error:1420C114:SSL routines:ssl_write_internal:uninitialized [SslError]
What arcanery am I missing?
If I get this working, I will generate a PR to add to the "net" library documentation.
(Running Nim 1.2.6 under an Ubuntu variant (PopOS))
I had countless issues with Ubuntu SSL, this does not surprise me.
It's probably dynamically linking with the wrong installed SSL. I would delete all SSL versions but the most resent ones. Can you list the SSL versions you have installed? Is it openssl or libressl?
You can also use httpClient (stdlib) for this:
import httpClient
let client = newHttpClient()
var content = client.getContent("https://google.com")
If you want to send custom https request use this:
import httpclient, json
let client = newHttpClient()
client.headers = newHttpHeaders({ "Content-Type": "application/json" })
let body = %*{
"username": "lum"
}
let response = client.request("https://strange.api", httpMethod = HttpPost, body = $body)
let content = body(response)
Remember: you need to compile it with -d:ssl too.My machine has OpenSSL 1.1.1f 31 Mar 2020 installed on it. I find no references to libressl.
And, I see the following libs:
./usr/lib/i386-linux-gnu/libssl.so.1.1
./usr/lib/x86_64-linux-gnu/libssl.so.1.1
Is openssl problematic?
I see a "nimssl" package in Nimble. I might play with that.
lum,
Thanks for the help!
My example is somewhat misleading as I was just trying to make a short and easy to read example of the problem. So I chose a simple web connection. I agree with you, if I was trying to reach a web server or any server that used a web api, httpClient would be the way to go.
The listed error kind of points to a failed SSL handshake. AFAIK, openssl requires some basic initilisaton steps. . Your example does not list them but I trust you're performing those steps?
import net, openssl
SSL_library_init()
OpenSSL_add_all_algorithms()
# perhaps also SSL_load_error_strings() and the elks?
Then, use the other 'connect', the one w/out the timeout which actually could raise an SslError. See if that returns any more detailed errors, SSL stack related.
This example was working a few months ago https://github.com/enthus1ast/nimSslExample
This also shows how a way to get self signed certificates working (not the best way imho)
Indeed the documentation in https://nim-lang.org/docs/httpclient.html#sslslashtls-support should be improved
Documentation on setting the SSL context is available at https://nim-lang.org/docs/net.html#newContext%2Cstring%2Cstring%2Cstring%2Cstring%2Cstring
Here is a working example: https://github.com/nim-lang/Nim/blob/devel/tests/stdlib/thttpclient_ssl.nim
Your example does not list them but I trust you're performing those steps?
You do not need to perform this initialisation! It is done for you by the stdlib implicitly if -d:ssl is passed to the compiler.
I don't see what the error in your code is @JohnAD. Is this a bug caused by the new SSL changes? (Does it work in 1.0?) Is the problem that a new SSL context isn't created?
@federico3 if this is a programmer bug then we shouldn't be crashing in such a confusing way. Can this be improved?
@dom96
What fixed it in the end was using the async methods. Apparently the non-async method is broken in some way.
So, the following does appear to work.
import asyncnet, net, asyncdispatch
proc main() {.async.} =
var ctx = newContext()
var next_socket = newAsyncSocket()
wrapSocket(ctx, next_socket)
await next_socket.connect("google.com", Port(443))
await next_socket.send("GET /")
waitFor main()
I'll be testing more tonight.
I've narrowed it down quite a bit:
import net
# proc mainAsync() {.async.} =
# var ctx = newContext()
# var next_socket = newAsyncSocket()
# wrapSocket(ctx, next_socket)
# await next_socket.connect("google.com", Port(443))
# await next_socket.send("GET /")
# proc mainSync() =
# var ctx = newContext()
# var next_socket = newSocket()
# wrapSocket(ctx, next_socket)
# next_socket.connect("google.com", Port(443))
# next_socket.send("GET /")
var ctx = newContext()
var next_socket = newSocket()
wrapSocket(ctx, next_socket)
next_socket.connect("google.com", Port(443))
next_socket.send("GET /")
This also works. The difference? I'm not calling the version of connect that takes the time parameter.