// Sample Xb2.NET client interface to a web based Personal Information Manager
// This sample illustrates the following:
//
// - how to write a web client app with Xb2.NET
// - how to connect through a proxy
// - how to maintain a persistent connection
// - how to create URL encoded forms
// - how to POST forms
// - how to GET files
// - how to handle cookies on the client side
// - how to handle a redirection
// - how to handle a 100-continue response
//-----------------------------------------------------------------------------
#include "xb2net.ch"
#include "common.ch"
#include "fileio.ch"
#pragma library ("xb2net.lib")
#xtranslate NTrim() => LTrim(Str())
#define CRLF (Chr(13)+Chr(10))
//-----------------------------------------------------------------------------
function Main()
Local oPIM, oUser, cColor
oPIM := PIMOnline():New()
//oPIM:ProxyHost := 'MyProxyURL'
//oPIM:ProxyPort := 8010
if ! oPIM:Connect()
MsgBox("Unable to connect to PIM server" + chr(10) +;
"Error code: " + NTrim(oPIM:Socket:ErrorCode) + " (" + oPIM:Socket:ErrorText(oPIM:Socket:ErrorCode) + ")" )
oPIM:destroy()
Return .f.
endif
? "connected to PIM server"
oUser := PIMUser():new()
oUser:LoginID := 'winter88'
oUser:Password := 'secret'
oUser:FirstName := 'Jack'
oUser:LastName := 'Frost'
oUser:EMail1 := 'jfrost@hotmail.com'
oUser:EMail2 := ''
oUser:TimeZone := '-300:Nassau'
oUser:ZIPCode := 'H0H 0H0'
oUser:ShowHolidays := .t.
oUser:DaylightSavings := .t.
oUser:Gender := 'M'
oUser:YearOfBirth := '1956'
oUser:Occupation := '14'
if oPIM:CreateNewUser( oUser )
? 'new user created, LoginID:',oUser:LoginID, ', password:', oUser:Password
else
? 'error, creating new user'
endif
?
? "Response from CreateNewUser:"
cColor := SetColor("w/b")
SaveToFile( oPIM:HTTPResponse:Content, "PIM-NewUser.htm" )
MemoEdit(oPIM:HTTPResponse:Content, Row()+1, 0, MaxRow(), MaxCol())
SetColor(cColor)
cls
? "try to login..."
if oPIM:Login(oUser:LoginID, oUser:Password, oUser:FirstName + " " + oUser:LastName)
? 'logged in successfully'
else
? 'error, unable to login'
endif
?
? 'Response from Login:'
cColor := SetColor("w/b")
SaveToFile( oPIM:HTTPResponse:Content, "PIM-Login.htm" )
MemoEdit(oPIM:HTTPResponse:Content, Row()+1, 0, MaxRow(), MaxCol())
oPIM:destroy()
Return .t.
//-----------------------------------------------------------------------------
STATIC FUNCTION SaveToFile( cText, cFileName )
Local i := 0, nHandle
while (nHandle := FCreate(cFileName)) < 0
if ++i > 9
Return .f.
endif
Sleep(20)
end
FWrite(nHandle, cText + CRLF)
FClose(nHandle)
Return .t.
//-----------------------------------------------------------------------------
CLASS PIMUser
EXPORTED:
VAR LoginID
VAR Password
VAR FirstName
VAR LastName
VAR EMail1, EMail2
VAR TimeZone
VAR ZIPCode
VAR ShowHolidays
VAR DaylightSavings
VAR Gender
VAR YearOfBirth
VAR Occupation
INLINE METHOD Init()
Return Self
ENDCLASS
//-----------------------------------------------------------------------------
CLASS PIMOnline
VAR Cookies
METHOD Post, Get, SaveCookie
EXPORTED:
VAR Socket
VAR HTTPRequest
VAR HTTPResponse
VAR ProxyHost, ProxyPort
VAR Host, Port
INLINE METHOD Init()
::Cookies := {}
::Port := 80
::Host := 'www.SomeURL.com'
::Socket := xbSocket():New(AF_INET, SOCK_STREAM, IPPROTO_IP)
::HTTPRequest := xbHTTPRequest():new( ::Socket )
::HTTPResponse := xbHTTPResponse():new( ::Socket )
Return self
INLINE METHOD SetProxy( cHost, nPort )
::ProxyHost := cHost
::ProxyPort := iif(empty(nPort), 80, nPort)
Return self
INLINE METHOD Connect()
if Empty(::ProxyHost)
Return ::Socket:Connect(::Host, ::Port)
endif
Return ::Socket:Connect(::ProxyHost, ::ProxyPort)
INLINE METHOD Destroy()
Return ::Socket:Destroy()
METHOD CreateNewUser
METHOD Login
ENDCLASS
METHOD PIMOnline:Post( cPath, oForm )
Local oRequest := ::HTTPRequest
Local oResponse := ::HTTPResponse
if !::Socket:Connected .and. !::Connect()
Return .f.
endif
// reset just in case these objects were used before
oRequest:Reset()
oResponse:Reset()
oRequest:Command := "POST"
// oRequest:HTTPVersion := '1.0' // we can set the version to 1.0 so we don't get a 100-continue response
oRequest:KeepAlive(.t.)
oRequest:CacheControl('no-cache')
oRequest:Host(::Host + ":" + NTrim(::Port))
// note: this sends back *all* cookies
// to be correct we should only send back the ones that belong this domain/path + have not expired
oRequest:Cookies := ::Cookies
if Empty(::ProxyHost)
oRequest:Path(cPath)
else
oRequest:Path("http://" + ::Host + ":" + NTrim(::Port) + cPath)
oRequest:SetHeader("Proxy-Connection", "keep-alive")
endif
oRequest:Content := oForm
if oRequest:send() == SOCKET_ERROR
Return .f.
endif
if ! oResponse:Recv()
Return .f.
endif
// we already sent the whole request so if we receive a 100-continue status, just ignore it
// do another :Recv to get the final response
if oResponse:StatusCode == 100 .and. ! oResponse:Recv()
Return .f.
endif
::SaveCookie( oResponse:GetCookie() )
if oResponse:StatusCode >= 300 .and. oResponse:StatusCode < 400
// redirection
Return ::Get("/" + oResponse:Location())
endif
Return .t.
METHOD PIMOnline:Get( cPath )
Local oRequest := ::HTTPRequest
Local oResponse := ::HTTPResponse
if !::Socket:Connected .and. !::Connect()
Return .f.
endif
// reset just in case these objects were used before
oRequest:Reset()
oResponse:Reset()
oRequest:Command := "GET"
// oRequest:HTTPVersion := '1.0' // we can set the version to 1.0 so we don't get a 100-continue response
oRequest:KeepAlive(.t.)
oRequest:CacheControl('no-cache')
oRequest:Host(::Host + ":" + NTrim(::Port))
// note: this sends back *all* cookies
// to be correct we should only send back the ones that belong this domain/path + have not expired
oRequest:Cookies := ::Cookies
// the path needs to be parsed because it may contain a query component
if Empty(::ProxyHost)
oRequest:xbURI:Parse(cPath)
else
oRequest:xbURI:Parse("http://" + ::Host + ":" + NTrim(::Port) + cPath)
oRequest:SetHeader("Proxy-Connection", "keep-alive")
endif
if oRequest:send() == SOCKET_ERROR
Return .f.
endif
if ! oResponse:Recv()
Return .f.
endif
// we already sent the whole request so if we receive a 100-continue status, just ignore it
// do another :Recv to get the final response
if oResponse:StatusCode == 100 .and. ! oResponse:Recv()
Return .f.
endif
::SaveCookie( oResponse:GetCookie() )
if oResponse:StatusCode >= 300 .and. oResponse:StatusCode < 400
// redirection
Return ::Get("/" + oResponse:Location())
endif
Return .t.
METHOD PIMOnline:SaveCookie( aNewCookies )
Local i, j, imax := len(aNewCookies)
for i := 1 to imax
// note: we should also be comparing the cookie path+domain and keeping track of it's expiry date
if (j := AScan(::Cookies, {|a|a[1]=aNewCookies[i,1]})) > 0
// new cookie replaces old one
::Cookies[j,2] := aNewCookies[i,2]
else
AAdd(::Cookies, {aNewCookies[i,1], aNewCookies[i,2]})
endif
next
Return self
METHOD PIMOnline:CreateNewUser( oUser )
Local oForm := xbForm():New()
oForm:SetVar('loginname' , oUser:LoginID )
oForm:SetVar('pass' , oUser:Password )
oForm:SetVar('pass2' , oUser:Password )
oForm:SetVar('firstname' , oUser:FirstName )
oForm:SetVar('lastname' , oUser:LastName )
oForm:SetVar('email' , oUser:EMail1 )
oForm:SetVar('email2' , oUser:EMail2 )
oForm:SetVar('timezone' , oUser:TimeZone )
oForm:SetVar('zip' , oUser:ZIPCode )
if oUser:ShowHolidays ; oForm:SetVar('U' , 'on'); endif
if oUser:DaylightSavings; oForm:SetVar('daylight', 'on'); endif
oForm:SetVar('gender' , oUser:Gender )
oForm:SetVar('yearofbirth', oUser:YearOfBirth)
oForm:SetVar('occupations', oUser:Occupation )
oForm:SetVar('submit1' , ' I Accept ')
if !::Post( '/newuserchk.asp', oForm )
Return .f.
endif
// if this string is present in response then new user has been created
Return "Congratulation " + oUser:FirstName + " " + oUser:LastName $ ::HTTPResponse:Content
METHOD PIMOnline:Login( cUID, cPWD, cFullName )
Local oForm := xbForm():New()
oForm:SetVar('loginname' , cUID )
oForm:SetVar('pass' , cPWD )
oForm:SetVar('validateLogin', '998877')
oForm:SetVar('submit1' , 'Enter')
if !::Post( '/checkuser.asp', oForm )
Return .f.
endif
// if this string is present in response then we are logged in
Return cFullName $ ::HTTPResponse:Content