/// <summary> /// Sends request to Clusterpoint Server. Returned CPS_Response should be casted to command-specific response class. /// </summary> /// <param name="request">Request to send.</param> /// <returns>Command-specific CPS_Response object instance.</returns> public CPS_Response sendRequest(CPS_Request request) { bool firstSend = true; string previousRenderedStorage = ""; string requestXml = ""; byte[] requestBytes = null; string rawResponse = ""; bool quit = true; if (this.p_connectionSwitcher != null) this.p_connectionSwitcher.newRequest(request); do { CPS_Exception e = null; if (this.p_connectionSwitcher != null) this.p_connectionString = this.parseConnectionString(this.p_connectionSwitcher.getConnectionString(ref this.p_storageName)); try { if (this.p_transactionId != null) request.setParam("transaction_id", this.p_transactionId); if (firstSend || previousRenderedStorage != this.p_storageName) { requestXml = this.renderRequest(request); requestBytes = Encoding.UTF8.GetBytes(requestXml); previousRenderedStorage = this.p_storageName; if (this.p_debug) { FileStream fs = new FileStream("request.xml", FileMode.Create); fs.Write(requestBytes, 0, requestBytes.Length); fs.Close(); } } firstSend = false; this.p_lastRequestSize = requestXml.Length; this.p_lastNetworkDuration = 0; Stopwatch totTimer = new Stopwatch(); Stopwatch netTimer = new Stopwatch(); totTimer.Start(); if (this.p_connectionString.Scheme.ToLower() == "http") { // TODO: implement HMAC support when server side supports it HttpWebRequest webreq = (HttpWebRequest)HttpWebRequest.Create(this.p_connectionString.OriginalString); webreq.UserAgent = this.p_applicationId; webreq.Method = "POST"; webreq.ContentType = "application/x-www-form-urlencoded"; webreq.ContentLength = requestBytes.Length; webreq.Headers["Recipient"] = this.p_storageName; webreq.Proxy = null; Stream webreq_data; try { webreq_data = webreq.GetRequestStream(); } catch (Exception) { throw new CPS_Exception("Invalid connection string").SetCode(CPS_Exception.ERROR_CODE.INVALID_CONNECTION_STRING); } netTimer.Start(); webreq_data.Write(requestBytes, 0, requestBytes.Length); webreq_data.Close(); netTimer.Stop(); HttpWebResponse webrsp; try { webrsp = (HttpWebResponse)webreq.GetResponse(); } catch (Exception) { throw new CPS_Exception("Invalid connection string").SetCode(CPS_Exception.ERROR_CODE.INVALID_CONNECTION_STRING); } Stream webrsp_data = webrsp.GetResponseStream(); StreamReader webrsp_reader = new StreamReader(webrsp_data); netTimer.Start(); rawResponse = webrsp_reader.ReadToEnd(); webrsp_reader.Close(); netTimer.Stop(); } if (this.p_connectionString.Scheme.ToLower() == "tcp" || this.p_connectionString.Scheme.ToLower() == "tcps") { int port = this.p_connectionString.Port; if (port <= 0) port = 5550; TcpClient tcp; try { netTimer.Start(); tcp = new TcpClient(this.p_connectionString.Host, port); netTimer.Stop(); } catch (SocketException) { netTimer.Stop(); throw new CPS_Exception("Cannot connect to specified server").SetCode(CPS_Exception.ERROR_CODE.SOCKET_ERROR); } catch (Exception) // all other cases { netTimer.Stop(); throw new CPS_Exception("Invalid connection string").SetCode(CPS_Exception.ERROR_CODE.INVALID_CONNECTION_STRING); } NetworkStream net = tcp.GetStream(); System.IO.Stream strm = net; if (this.p_connectionString.Scheme.ToLower() == "tcps") { System.Net.Security.SslStream ssl = new System.Net.Security.SslStream(strm, false, new System.Net.Security.RemoteCertificateValidationCallback(ConnectionServerValidationCallback), null); try { ssl.AuthenticateAsClient(this.p_connectionString.Host); } catch (Exception) { throw new CPS_Exception("Error establishing SSL connection").SetCode(CPS_Exception.ERROR_CODE.SSL_HANDSHAKE); } strm = ssl; } Protobuf pb = new Protobuf(); pb.CreateField(1, Protobuf.WireType.LengthDelimited, requestBytes); pb.CreateStringField(2, this.p_storageName); if (this.p_hmacUserKey != null && this.p_hmacSignKey != null) { string characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; char[] tokenchars = new char[16]; for (int i = 0; i < 16; i++) tokenchars[i] = characters[this.p_random.Next(characters.Length)]; string token = new string(tokenchars); System.DateTime epoch = new System.DateTime(1970, 1, 1, 0, 0, 0, 0).ToUniversalTime(); System.DateTime validity = System.DateTime.Now.ToUniversalTime(); validity.AddSeconds(10); System.TimeSpan dtdiff = validity - epoch; UInt64 unixvalidity = (UInt64)dtdiff.TotalSeconds; pb.CreateStringField(14, token); pb.CreateFixed64Field(15, unixvalidity); pb.CreateStringField(16, this.p_hmacUserKey); pb.CreateStringField(17, CPS_Hasher.SHA1(CPS_Hasher.SHA1(requestXml) + token + unixvalidity + this.p_hmacSignKey)); pb.CreateStringField(18, CPS_Hasher.SHA1_HMAC(this.p_hmacSignKey, requestXml + token + unixvalidity)); } MemoryStream ms = new MemoryStream(); Protobuf_Streamer pbs = new Protobuf_Streamer(ms); pb.WriteToStream(pbs); byte[] header = CPS_Length2Header((int)(ms.Length)); netTimer.Start(); try { strm.Write(header, 0, 8); strm.Write(ms.GetBuffer(), 0, (int)(ms.Length)); } catch (Exception) { netTimer.Stop(); throw new CPS_Exception("Error sending request").SetCode(CPS_Exception.ERROR_CODE.SOCKET_ERROR); } strm.Flush(); try { strm.Read(header, 0, 8); netTimer.Stop(); } catch (Exception) { netTimer.Stop(); throw new CPS_Exception("Error receiving response").SetCode(CPS_Exception.ERROR_CODE.SOCKET_ERROR); } int len = CPS_Header2Length(header); if (len <= 0) throw new CPS_Exception("Invalid response from server").SetCode(CPS_Exception.ERROR_CODE.INVALID_RESPONSE); byte[] recv = new byte[len]; int got = 0; netTimer.Start(); while (got < len) { int br = 0; try { br = strm.Read(recv, got, len - got); if (br == 0) { netTimer.Stop(); throw new CPS_Exception("Server unexpectedly closed connection").SetCode(CPS_Exception.ERROR_CODE.SOCKET_ERROR); } } catch (Exception) { netTimer.Stop(); throw new CPS_Exception("Error receiving response").SetCode(CPS_Exception.ERROR_CODE.SOCKET_ERROR); } got += br; } strm.Close(); netTimer.Stop(); ms = new MemoryStream(recv); pbs = new Protobuf_Streamer(ms); pb = new Protobuf(pbs); rawResponse = pb.GetStringField(1); } totTimer.Stop(); this.p_lastRequestDuration = totTimer.ElapsedMilliseconds; this.p_lastRequestDuration = this.p_lastRequestDuration / 1000.0; this.p_lastNetworkDuration = netTimer.ElapsedMilliseconds; this.p_lastNetworkDuration = this.p_lastNetworkDuration / 1000.0; this.p_lastResponseSize = rawResponse.Length; if (this.p_debug) { FileStream fs = new FileStream("response.xml", FileMode.Create); byte[] responseBytes = Encoding.UTF8.GetBytes(rawResponse); fs.Write(responseBytes, 0, responseBytes.Length); fs.Close(); } } catch (CPS_Exception e_) { e = e_; } if (this.p_connectionSwitcher != null) quit = !this.p_connectionSwitcher.shouldRetry(rawResponse, e); else quit = true; if (quit && e != null) throw e; } while (!quit); switch (request.getCommand()) { case "search": case "similar": return new CPS_SearchResponse(this, request, rawResponse); case "update": case "delete": case "replace": case "partial-replace": case "partial-xreplace": case "insert": case "create-alert": case "update-alerts": case "delete-alerts": return new CPS_ModifyResponse(this, request, rawResponse); case "alternatives": return new CPS_AlternativesResponse(this, request, rawResponse); case "list-words": return new CPS_ListWordsResponse(this, request, rawResponse); case "status": return new CPS_StatusResponse(this, request, rawResponse); case "retrieve": case "list-last": case "list-first": case "retrieve-last": case "retrive-first": case "lookup": case "show-history": return new CPS_LookupResponse(this, request, rawResponse); case "search-delete": return new CPS_SearchDeleteResponse(this, request, rawResponse); case "list-paths": return new CPS_ListPathsResponse(this, request, rawResponse); case "list-facets": return new CPS_ListFacetsResponse(this, request, rawResponse); case "list-alerts": return new CPS_Response(this, request, rawResponse); // TODO: change this !!! default: CPS_Response ret = new CPS_Response(this, request, rawResponse); // This is explicitly processed here, because of .NET limitations. PHP API changes this directly from CPS_Response constructor. if (request.getCommand() == "begin-transaction" || request.getCommand() == "commit-transaction" || request.getCommand() == "rollback-transaction") this.p_transactionId = ret.getTransactionId(); return ret; } }
/// <summary> /// Renders request XML for sending to storage. /// </summary> /// <param name="request">Request instance to render.</param> /// <returns>XML request as string.</returns> private string renderRequest(CPS_Request request) { Dictionary<string, string> envelopeFields = new Dictionary<string, string>(); envelopeFields["storage"] = this.p_storageName; envelopeFields["user"] = this.p_username; envelopeFields["password"] = this.p_password; envelopeFields["command"] = request.getCommand(); foreach (KeyValuePair<string, string> cep in this.p_customEnvelopeParams) envelopeFields[cep.Key] = cep.Value; if (request.getRequestId().Length > 0) envelopeFields["requestid"] = request.getRequestId(); if (this.p_applicationId.Length > 0) envelopeFields["application"] = this.p_applicationId; if (request.getRequestType().Length > 0) envelopeFields["type"] = request.getRequestType(); if (request.getClusterLabel().Length > 0) envelopeFields["label"] = request.getClusterLabel(); return request.getRequestXML(this.p_documentRootXpath, this.p_documentIdXpath, envelopeFields, this.p_docid_resetting); }
/// <summary> /// This is called by CPS_Connection whenever there is a new request to be sent. This method should not be used directly. /// </summary> /// <param name="request">The request before being rendered.</param> public void newRequest(CPS_Request request) { if (request.getCommand() == "search") this.p_retryRequests = true; else this.p_retryRequests = false; if (this.p_numUsed == this.p_connectionStrings.Count) { this.p_usedConnections.Clear(); this.p_numUsed = 0; this.p_lastReturnedIndex = -1; this.p_lastSuccess = false; } }