Small sockets - posting to usenet in Dolphin Smalltalk
Published: 10:09 PM GMT+12, Monday, 6 September 2004 under:
technology smalltalk
The other day someone in comp.lang.smalltalk.dolphin posted asking about sockets and "direct access to the internet" in Dolphin Smalltalk. Frankly, I still have no idea exactly what he was asking, but the subject at least made sense: "Newsgroup posts via Dolphin". Now, I'm somewhat new to Smalltalk, and Dolphin in particular, but I once said to James Robertson ages ago that I wanted a small project in Smalltalk to "give me something to learn by" and do, and so I decided I'd write a simple NNTP poster... As always we start with an empty class and an SUnit testcase and add some tests:
testConnect | nntp | nntp := NNTPClient new. self assert: (nntp connect: 'library.airnews.net' port: 119)
testPost
| nntp |
nntp := NNTPClient new.
nntp authenticate: 'talios' with: '****'.
(nntp connect: 'library.airnews.net' port: 119)
ifTrue:
[self
assert: (nntp
postTo: 'alt.test'
from: 'talios@gmail.com'
subject: 'Test'
body: 'Test Post')]
So I save these methods, and instantly they fail. For one, theres no #authenticate, #connect, or #postTo messages. So lets start coding...
A quick Google of the relevant RFCs and I have some info on what I have to do to connect, so I write #connect:
Thats pretty straight forward, set some instance variables, open a socket, and check to see we get the expected 200 result code. The original version of the code didn't check for any authentication of the user credentials, but as I use Airnews as my usenet source ( which requires authentication ) the tests pretty quickly failed on me, so along comes some authentication handling code...connect: aHostName port: aPortNumber "Connect to a server" host := aHostName. port := aPortNumber. socket := Socket port: port address: (InternetAddress host: host). socket connect. ^'200*' match: socket readStream readPage collection asString
The authenticate method doesn't actually do anything other than store some more instance variables for later use... Now we come to the guts of the code...authenticate: aUser with: aPassword "Set Authenticatation credentials..." user := aUser. password := aPassword
postTo: aGroupName from: aSender subject: aSubject body: aBody
"Send the message..."
| input |
(socket writeStream)
nextPutAll: 'POST' , String lineDelimiter;
flush.
input := socket readStream readPage collection asString.
('480*' match: input) ifTrue: [self sendAuthenticationCredentials].
(socket writeStream)
nextPutAll: 'Newsgroups: ' , aGroupName , String lineDelimiter;
nextPutAll: 'From: ' , aSender , String lineDelimiter;
nextPutAll: 'Subject: ' , aSubject , String lineDelimiter;
nextPutAll: String lineDelimiter;
nextPutAll: aBody , String lineDelimiter;
nextPutAll: '.' , String lineDelimiter;
flush.
^'240*' match: socket readStream readPage collection asString.
Transcript show: 'NNTP post to ' , aGroupName , ' by ' , aSender , ' sent successfully.'
Here we write POST to the socket and check for a 480 ( authentication required ) result - if it exists #sendAuthenticationCredentials to the server and carry on. I really should be checking for the 340 result code if we didn't receive the 480, but for now, I don't really care...
Continuing on we send the usenet posting out to the socket, terminate it with a . on a newline, and check for the 240 success result code.
But whats happening in #sendAuthenticationCredentials?
sendAuthenticationCredentials
"Send the authentication credentials."
| input |
(socket writeStream)
nextPutAll: 'AUTHINFO user ' , user , String lineDelimiter;
flush.
('381*' match: socket readStream readPage collection asString)
ifTrue:
[(socket writeStream)
nextPutAll: 'AUTHINFO pass ' , password , String lineDelimiter;
flush.
input := socket readStream readPage collection asString.
('281*' match: input) ifFalse: [^false]]
ifFalse: [^false]
As I said above, I've very new to Smalltalk, and Dolphin, so I've probably done things here that could easily be tidied up or improved on, but the code does work, and passes the tests... IMHO, thats the main thing...
If any seasoned Smalltalkers out there want to improve and optimize on this, tell me how I can improve on it.... ( of course, trapping the full RFC is a given there....