/// <summary>
        /// Request the specified mimeStream, methods and session.
        /// </summary>
        /// <returns>The request.</returns>
        /// <param name="mimeStream">MIME stream.</param>
        /// <param name="methods">Methods.</param>
        /// <param name="session">Session.</param>
        public static ShippingApiMethodRequest Request(MimeStream mimeStream, List <ShippingApiMethod> methods, ISession session)
        {
            mimeStream.ReadHeaders(); // reads http headers as well

            Dictionary <string, string> headers = new Dictionary <string, string>();

            foreach (var h in mimeStream.Headers.Keys)
            {
                StringBuilder sb    = new StringBuilder();
                bool          first = true;
                foreach (var s in mimeStream.Headers[h])
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        sb.Append(';');
                    }
                    sb.Append(s);
                }
                headers.Add(h, sb.ToString());
            }

            var    firstLine = mimeStream.FirstLine.Split(' ');
            var    verb      = firstLine[0];
            string pattern   = "(?<file>/[a-zA-Z0-9/]+)(\\?(?<parameters>.*))*";
            var    urlRegex  = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
            var    match     = urlRegex.Match(firstLine[1]);

            if (!match.Success)
            {
                throw new Exception("Could not parse URI");
            }
            var uri        = match.Groups[urlRegex.GroupNumberFromName("file")].Value;
            var parameters = match.Groups[urlRegex.GroupNumberFromName("parameters")].Value;

            var requestParameters = new Dictionary <string, string>();

            if (parameters != null && !parameters.Equals(""))
            {
                var p1 = parameters.Split('&');
                foreach (var p2 in p1)
                {
                    var h = p2.Split('=');
                    requestParameters[h[0]] = h[1];
                }
            }

            var deserializer = new JsonSerializer();

            deserializer.Error           += DeserializationError;
            deserializer.ContractResolver = new ShippingApiContractResolver();
            if (session.TraceWriter != null)
            {
                deserializer.TraceWriter = session.TraceWriter;
            }

            foreach (var method in methods)
            {
                if (method.Verb.ToString() != verb)
                {
                    continue;
                }
                var re = new Regex(method.UriRegex, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
                var m  = re.Match(uri);
                if (m.Success)
                {
                    IShippingApiRequest request = null;
                    // create the request
                    using (var reader = new StreamReader(mimeStream))
                    {
                        ((ShippingApiContractResolver)deserializer.ContractResolver).Registry = session.SerializationRegistry;
                        // if wrapped create wrapper object
                        if (method.RequestInterface != null)
                        {
                            var    obj         = deserializer.Deserialize(reader, method.RequestType);
                            Type[] typeArgs    = { obj.GetType() };
                            var    wrapperType = session.SerializationRegistry.GetWrapperFor(method.RequestInterface).MakeGenericType(typeArgs);
                            request = (IShippingApiRequest)Activator.CreateInstance(wrapperType, obj);
                        }
                        else
                        {
                            request = (IShippingApiRequest)deserializer.Deserialize(reader, method.RequestType);
                        }
                    }

                    // set params in the URI
                    for (int g = 0; g < m.Groups.Count; g++)
                    {
                        var paramName = re.GroupNameFromNumber(g);
                        // set property of the request matching capture name
                        if (!Regex.IsMatch(paramName, "^\\d+$"))
                        {
                            PropertyInfo prop = method.RequestType.GetProperty("paramName");
                            prop.SetValue(request, match.Groups[g].Value, null);
                        }
                    }

                    // query properties
                    ShippingApiRequest.ProcessRequestAttributes <ShippingApiQueryAttribute>(request,
                                                                                            (a, s, v, p) => {
                        // p is prop name
                        if (requestParameters.ContainsKey(s))
                        {
                            PropertyInfo prop = request.GetType().GetProperty(p);
                            //TODO: better handling of JSON encoding
                            var sb = new StringBuilder(requestParameters[s]).Replace("\"", "\\\"").Append("\"");
                            sb.Insert(0, "\"");
                            var tx = new StringReader(sb.ToString());
                            var o  = deserializer.Deserialize(tx, prop.PropertyType);
                            prop.SetValue(request, o);
                        }
                    }
                                                                                            );

                    // header properties
                    ShippingApiRequest.ProcessRequestAttributes <ShippingApiHeaderAttribute>(request,
                                                                                             (a, s, v, p) => {
                        foreach (var h in headers)
                        {
                            if (h.Key.ToLower() == s.ToLower())
                            {
                                PropertyInfo prop = request.GetType().GetProperty(p);
                                var sb            = new StringBuilder(h.Value).Replace("\"", "\\\"").Append("\"");
                                sb.Insert(0, "\"");
                                var tx = new StringReader(sb.ToString());
                                var o  = deserializer.Deserialize(tx, prop.PropertyType);
                                prop.SetValue(request, o);
                                break;
                            }
                        }
                    }
                                                                                             );
                    return(new ShippingApiMethodRequest()
                    {
                        Method = method, Request = request
                    });
                }
            }
            return(null);
        }
#pragma warning disable CS1998 // No async operations in mock
        /// <summary>
        /// Implements the same method as the real service interface allowing the mock to be plugged in
        /// </summary>
        /// <typeparam name="Response"></typeparam>
        /// <typeparam name="Request"></typeparam>
        /// <param name="resource"></param>
        /// <param name="verb"></param>
        /// <param name="request"></param>
        /// <param name="deleteBody"></param>
        /// <param name="session"></param>
        /// <returns></returns>
        public async Task <ShippingApiResponse <Response> > HttpRequest <Response, Request>(string resource, HttpVerb verb, Request request, bool deleteBody, ISession session = null) where Request : IShippingApiRequest
        {
            string fullPath = request.RecordingFullPath(resource, session);

            if (File.Exists(fullPath))
            {
                var apiResponse = new ShippingApiResponse <Response> {
                    HttpStatus = HttpStatusCode.OK, Success = true
                };
                long jsonPosition = 0;
                using (var fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read))
                    using (var mimeStream = new MimeStream(fileStream))
                    {
                        mimeStream.SeekNextPart(); //request
                        mimeStream.SeekNextPart(); //response
                        mimeStream.ClearHeaders();
                        mimeStream.ReadHeaders();  // reads http headers as well
                        if (!mimeStream.FirstLine.StartsWith("HTTP", StringComparison.InvariantCulture))
                        {
                            apiResponse = new ShippingApiResponse <Response> {
                                HttpStatus = HttpStatusCode.InternalServerError, Success = false
                            };
                            session.LogDebug(string.Format("Mock request failed {0}", fullPath));
                            apiResponse.Errors.Add(new ErrorDetail()
                            {
                                ErrorCode = "Mock 500", Message = "Bad format " + fullPath
                            });
                            return(apiResponse);
                        }
                        var hrc = mimeStream.FirstLine.Split(' ');
                        apiResponse.HttpStatus = (HttpStatusCode)int.Parse(hrc[1]);
                        apiResponse.Success    = apiResponse.HttpStatus == HttpStatusCode.OK;

                        foreach (var h in mimeStream.Headers)
                        {
                            apiResponse.ProcessResponseAttribute(h.Key, h.Value);
                        }

                        using (var recordingStream = new RecordingStream(mimeStream, request.RecordingFullPath(resource, session), FileMode.Create, RecordingStream.RecordType.PlainText))
                        {
                            try
                            {
                                //dont open the record file
                                recordingStream.IsRecording = false;
                                ShippingApiResponse <Response> .Deserialize(session, recordingStream, apiResponse, jsonPosition);
                            }
                            catch (Exception ex)
                            {
                                session.LogError(string.Format("Mock request {0} got deserialization exception {1}", fullPath, ex.Message));
                                throw ex;
                            }
                        }
                    }
                return(apiResponse);
            }

            else
            {
                var apiResponse = new ShippingApiResponse <Response> {
                    HttpStatus = HttpStatusCode.NotFound, Success = false
                };
                session.LogDebug(string.Format("Mock request failed {0}", fullPath));
                apiResponse.Errors.Add(new ErrorDetail()
                {
                    ErrorCode = "Mock 401", Message = "Could not find response file" + fullPath
                });
                return(apiResponse);
            }
        }