/// <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> /// Creates new empty instance of CPS Response class without parsing response XML. /// </summary> /// <param name="connection">CPS_Connection class instance.</param> /// <param name="request">CPS_Request class instance.</param> protected CPS_Response(CPS_Connection connection, CPS_Request request) { this.p_connection = connection; this.p_transactionId = null; this.p_simpleXml = null; this.p_errors = new List<CPS_SimpleXML>(); this.p_textParams = new Dictionary<string, string>(); this.p_contentArray = null; this.p_documents = new Dictionary<string, XmlElement>(); this.p_facets = new Dictionary<string, Dictionary<string, int>>(); this.p_words = null; this.p_wordCounts = new Dictionary<string, int>(); this.p_aggregate = new Dictionary<string, List<XmlElement>>(); }
/// <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> /// 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; } }
/// <summary> /// Creates new instance of CPS Response class. /// </summary> /// <param name="connection">CPS_Connection class instance.</param> /// <param name="request">CPS_Request class instance.</param> /// <param name="responseXml">Raw response XML as string.</param> public CPS_Response(CPS_Connection connection, CPS_Request request, string responseXml) { this.p_connection = connection; this.p_transactionId = null; this.p_simpleXml = new XmlDocument(); try { this.p_simpleXml.LoadXml(responseXml); } catch (Exception) { throw new CPS_Exception("Received invalid XML response").SetCode(CPS_Exception.ERROR_CODE.INVALID_RESPONSE); } XmlElement content = null; this.p_errors = new List<CPS_SimpleXML>(); foreach (XmlElement xmle in this.p_simpleXml.DocumentElement.ChildNodes) { if (xmle.Name == "cps:storage") this.p_storageName = xmle.InnerXml; else if (xmle.Name == "cps:command") this.p_command = xmle.InnerXml; else if (xmle.Name == "cps:seconds") { try { this.p_seconds = float.Parse(xmle.InnerXml, System.Globalization.CultureInfo.InvariantCulture.NumberFormat); } catch (Exception) { this.p_seconds = 0; } } else if (xmle.Name == "cps:error") { this.p_errors.Add(new CPS_SimpleXML(xmle)); } else if (xmle.Name == "cps:content") content = xmle; } foreach (CPS_SimpleXML sxer in this.p_errors) { string elvl = sxer["level"]; if (elvl == "REJECTED" || elvl == "FAILED" || elvl == "ERROR" || elvl == "FATAL") throw new CPS_Exception(sxer["message"]).SetCode(sxer["code"]); } this.p_textParams = new Dictionary<string, string>(); this.p_contentArray = null; this.p_documents = new Dictionary<string, XmlElement>(); this.p_facets = new Dictionary<string, Dictionary<string, int>>(); bool saveDocs = true; this.p_words = null; this.p_wordCounts = new Dictionary<string, int>(); this.p_aggregate = new Dictionary<string, List<XmlElement>>(); if (content != null) { switch (this.p_command) { case "update": case "delete": case "insert": case "partial-replace": case "partial-xreplace": case "replace": case "lookup": if (this.p_command != "lookup") saveDocs = false; break; case "search": case "retrieve": case "retrieve-last": case "retrieve-first": case "list-last": case "list-first": case "similar": case "show-history": case "cursor-next-batch": // In PHP here comes document extraction. In C# document extraction is later, so nothing to do here break; case "alternatives": Dictionary<string, Dictionary<string, CPS_AlternativesResponse.WordInfo>> alt_words = new Dictionary<string, Dictionary<string, CPS_AlternativesResponse.WordInfo>>(); foreach (XmlElement xaltlist in content.ChildNodes) foreach (XmlElement xalt in xaltlist.ChildNodes) { if (xalt.Name == "alternatives") { Dictionary<string, CPS_AlternativesResponse.WordInfo> winfo = new Dictionary<string, CPS_AlternativesResponse.WordInfo>(); XmlElement xto = xalt["to"]; if (xto == null) continue; XmlElement xto_count = xalt["count"]; if (xto_count == null) continue; int ito_count = 0; try { ito_count = int.Parse(xto_count.InnerXml); } catch (Exception) { continue; } foreach (XmlElement xword in xalt.ChildNodes) { if (xword.Name == "word") { XmlAttribute xcount = xword.Attributes["count"]; XmlAttribute xcr = xword.Attributes["cr"]; XmlAttribute xidif = xword.Attributes["idif"]; XmlAttribute xh = xword.Attributes["h"]; if (xcount == null || xcr == null || xidif == null || xh == null) continue; CPS_AlternativesResponse.WordInfo info = new CPS_AlternativesResponse.WordInfo(); info.word = xword.InnerXml; try { info.count = int.Parse(xcount.Value); info.cr = float.Parse(xcr.Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat); info.idif = float.Parse(xidif.Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat); info.h = float.Parse(xh.Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat); } catch (Exception) { continue; } winfo[info.word] = info; } } alt_words[xto.InnerXml] = winfo; this.p_wordCounts[xto.InnerXml] = ito_count; } } this.p_words = alt_words; break; case "list-words": Dictionary<string, Dictionary<string, CPS_ListWordsResponse.WordInfo>> list_words = new Dictionary<string, Dictionary<string, CPS_ListWordsResponse.WordInfo>>(); foreach (XmlElement xlist in content.ChildNodes) { if (xlist.Name == "list") { Dictionary<string, CPS_ListWordsResponse.WordInfo> winfo = new Dictionary<string, CPS_ListWordsResponse.WordInfo>(); XmlAttribute xto = xlist.Attributes["to"]; if (xto == null) continue; foreach (XmlElement xword in xlist.ChildNodes) { if (xword.Name == "word") { XmlAttribute xcount = xword.Attributes["count"]; if (xcount == null) continue; CPS_ListWordsResponse.WordInfo info = new CPS_ListWordsResponse.WordInfo(); info.word = xword.InnerXml; try { info.count = int.Parse(xcount.Value); } catch (Exception) { continue; } winfo[info.word] = info; } } list_words[xto.Value] = winfo; } } this.p_words = list_words; break; case "status": case "list-paths": case "list-databases": case "create-database": case "edit-database": case "rename-database": case "drop-database": case "describe-database": case "list-nodes": case "list-hubs": case "list-hosts": case "set-offline": case "set-online": case "list-alerts": this.p_contentArray = content; break; case "begin-transaction": try { this.p_transactionId = content["transaction_id"].InnerXml; } catch (Exception) { this.p_transactionId = null; } break; case "commit-transaction": case "rollback-transaction": this.p_transactionId = null; break; default: // nothing special break; } if (this.p_command == "search" || this.p_command == "list-facets") { foreach (XmlElement xmle in content.ChildNodes) { if (xmle.Name == "facet") { XmlAttribute xattr = xmle.Attributes["path"]; if (xattr == null || xattr.Value.Length <= 0) continue; string path = xattr.Value; this.p_facets[path] = new Dictionary<string, int>(); foreach (XmlElement xmlt in xmle.ChildNodes) { if (xmlt.Name == "term") { this.p_facets[path][xmlt.InnerXml] = 0; if (this.p_command == "search") { xattr = xmlt.Attributes["hits"]; if (xattr != null && xattr.Value.Length > 0) { try { this.p_facets[path][xmlt.InnerXml] = int.Parse(xattr.Value); } catch (Exception) { } } } } } } if (xmle.Name == "aggregate") { string query = ""; try { XmlElement qel = xmle["query"]; if (qel != null) query = qel.InnerXml; } catch (Exception) { } if (query.Length > 0) { this.p_aggregate[query] = new List<XmlElement>(); foreach (XmlElement xmld in xmle.ChildNodes) if (xmld.Name == "data") this.p_aggregate[query].Add(xmld); } } } } foreach (XmlElement xmle in content.ChildNodes) { if (Array.Exists<string>(CPS_Response.p_textParamNames, pn => pn == xmle.Name)) this.p_textParams[xmle.Name] = xmle.InnerXml; else if (xmle.Name == "results") { foreach (XmlElement xmld in xmle.ChildNodes) this.ProcessRawDocument(connection, xmld, saveDocs); } else if (xmle.Name == connection.getDocumentRootXpath()) this.ProcessRawDocument(connection, xmle, saveDocs); } } }
/// <summary> /// Creates new CPS_ModifyResponse class instance. /// </summary> /// <param name="connection">CPS_Connection class instance.</param> /// <param name="request">CPS_Request class instance.</param> /// <param name="responseXml">Raw response XML as string.</param> public CPS_ModifyResponse(CPS_Connection connection, CPS_Request request, string responseXml) : base(connection, request, responseXml) { }
/// <summary> /// Creates new CPS_ListWordsResponse class instance. /// </summary> /// <param name="connection">CPS_Connection class instance.</param> /// <param name="request">CPS_Request class instance.</param> /// <param name="responseXml">Raw response XML as string.</param> public CPS_ListWordsResponse(CPS_Connection connection, CPS_Request request, string responseXml) : base(connection, request, responseXml) { }
/// <summary> /// Creates new CPS_EmptyResponse class instance. /// </summary> /// <param name="connection">CPS_Connection class instance.</param> /// <param name="request">CPS_Request class instance.</param> public CPS_EmptyResponse(CPS_Connection connection, CPS_Request request) : base(connection, request) { }
/// <summary> /// Creates new CPS_SearchDeleteResponse class instance. /// </summary> /// <param name="connection">CPS_Connection class instance.</param> /// <param name="request">CPS_Request class instance.</param> /// <param name="responseXml">Raw response XML as string.</param> public CPS_SearchDeleteResponse(CPS_Connection connection, CPS_Request request, string responseXml) : base(connection, request, responseXml) { }
/// <summary> /// Creates new CPS_AlternativesResponse class instance. /// </summary> /// <param name="connection">CPS_Connection class instance.</param> /// <param name="request">CPS_Request class instance.</param> /// <param name="responseXml">Raw response XML as string.</param> public CPS_AlternativesResponse(CPS_Connection connection, CPS_Request request, string responseXml) : base(connection, request, responseXml) { this.p_localWords = null; }