mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 09:15:27 +08:00 
			
		
		
		
	started refactoring request builder
This commit is contained in:
		@@ -46,7 +46,7 @@ namespace Ocelot.DependencyInjection
 | 
			
		||||
            services.AddLogging();
 | 
			
		||||
 | 
			
		||||
            // ocelot services.
 | 
			
		||||
            services.AddSingleton<IRemoveHeaders, RemoveHeaders>();
 | 
			
		||||
            services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
 | 
			
		||||
            services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
 | 
			
		||||
            services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
 | 
			
		||||
            services.AddSingleton<IAuthoriser, ClaimsAuthoriser>();
 | 
			
		||||
@@ -60,7 +60,7 @@ namespace Ocelot.DependencyInjection
 | 
			
		||||
            services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
 | 
			
		||||
            services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
 | 
			
		||||
            services.AddSingleton<IHttpResponder, HttpContextResponder>();
 | 
			
		||||
            services.AddSingleton<IRequestBuilder, HttpRequestBuilder>();
 | 
			
		||||
            services.AddSingleton<IRequestCreator, HttpRequestCreator>();
 | 
			
		||||
            services.AddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
 | 
			
		||||
            services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
 | 
			
		||||
            services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Headers
 | 
			
		||||
{
 | 
			
		||||
    public interface IRemoveHeaders
 | 
			
		||||
    public interface IRemoveOutputHeaders
 | 
			
		||||
    {
 | 
			
		||||
        Response Remove(HttpResponseHeaders headers);
 | 
			
		||||
    }
 | 
			
		||||
@@ -1,22 +1,20 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Net.Http.Headers;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Headers
 | 
			
		||||
{
 | 
			
		||||
    public class RemoveHeaders : IRemoveHeaders
 | 
			
		||||
    public class RemoveOutputHeaders : IRemoveOutputHeaders
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Some webservers return headers that cannot be forwarded to the client
 | 
			
		||||
        /// in a given context such as transfer encoding chunked when ASP.NET is not
 | 
			
		||||
        /// returning the response in this manner
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private readonly List<string> _unsupportedHeaders = new List<string>
 | 
			
		||||
        private readonly string[] _unsupportedHeaders = 
 | 
			
		||||
        {
 | 
			
		||||
            "Transfer-Encoding"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public Response Remove(HttpResponseHeaders headers)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var unsupported in _unsupportedHeaders)
 | 
			
		||||
@@ -1,109 +0,0 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net.Http.Headers;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Request.Builder
 | 
			
		||||
{
 | 
			
		||||
    public class HttpRequestBuilder : IRequestBuilder
 | 
			
		||||
    {
 | 
			
		||||
        public async Task<Response<Request>> Build(
 | 
			
		||||
            string httpMethod, 
 | 
			
		||||
            string downstreamUrl, 
 | 
			
		||||
            Stream content, 
 | 
			
		||||
            IHeaderDictionary headers,
 | 
			
		||||
            IRequestCookieCollection cookies, 
 | 
			
		||||
            QueryString queryString, 
 | 
			
		||||
            string contentType, 
 | 
			
		||||
            RequestId.RequestId requestId)
 | 
			
		||||
        {
 | 
			
		||||
            var method = new HttpMethod(httpMethod);
 | 
			
		||||
 | 
			
		||||
            var uri = new Uri(string.Format("{0}{1}", downstreamUrl, queryString.ToUriComponent()));
 | 
			
		||||
 | 
			
		||||
            var httpRequestMessage = new HttpRequestMessage(method, uri);
 | 
			
		||||
 | 
			
		||||
            if (content != null)
 | 
			
		||||
            {
 | 
			
		||||
                httpRequestMessage.Content = new ByteArrayContent(await ToByteArray(content));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrEmpty(contentType))
 | 
			
		||||
            {
 | 
			
		||||
                httpRequestMessage.Content.Headers.Remove("Content-Type");
 | 
			
		||||
                httpRequestMessage.Content.Headers.TryAddWithoutValidation("Content-Type", contentType); 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //todo get rid of if
 | 
			
		||||
            if (headers != null)
 | 
			
		||||
            {
 | 
			
		||||
                headers.Remove("Content-Type");
 | 
			
		||||
 | 
			
		||||
                foreach (var header in headers)
 | 
			
		||||
                {
 | 
			
		||||
                    //todo get rid of if..
 | 
			
		||||
                    if (header.Key.ToLower() != "host")
 | 
			
		||||
                    {
 | 
			
		||||
                        httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (RequestKeyIsNotNull(requestId) && !RequestIdInHeaders(requestId, httpRequestMessage.Headers))
 | 
			
		||||
            {
 | 
			
		||||
                ForwardRequestIdToDownstreamService(requestId, httpRequestMessage);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cookieContainer = new CookieContainer();
 | 
			
		||||
 | 
			
		||||
            //todo get rid of if
 | 
			
		||||
            if (cookies != null)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var cookie in cookies)
 | 
			
		||||
                {
 | 
			
		||||
                    cookieContainer.Add(uri, new Cookie(cookie.Key, cookie.Value));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return new OkResponse<Request>(new Request(httpRequestMessage, cookieContainer));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ForwardRequestIdToDownstreamService(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage)
 | 
			
		||||
        {
 | 
			
		||||
            httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool RequestIdInHeaders(RequestId.RequestId requestId, HttpRequestHeaders headers)
 | 
			
		||||
        {
 | 
			
		||||
            IEnumerable<string> value;
 | 
			
		||||
            if (headers.TryGetValues(requestId.RequestIdKey, out value))
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool RequestKeyIsNotNull(RequestId.RequestId requestId)
 | 
			
		||||
        {
 | 
			
		||||
            return !string.IsNullOrEmpty(requestId?.RequestIdKey) && !string.IsNullOrEmpty(requestId.RequestIdValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<byte[]> ToByteArray(Stream stream)
 | 
			
		||||
        {
 | 
			
		||||
            using (stream)
 | 
			
		||||
            {
 | 
			
		||||
                using (var memStream = new MemoryStream())
 | 
			
		||||
                {
 | 
			
		||||
                    await stream.CopyToAsync(memStream);
 | 
			
		||||
                    return memStream.ToArray();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/Ocelot/Request/Builder/HttpRequestCreator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/Ocelot/Request/Builder/HttpRequestCreator.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Request.Builder
 | 
			
		||||
{
 | 
			
		||||
    public sealed class HttpRequestCreator : IRequestCreator
 | 
			
		||||
    {
 | 
			
		||||
        public async Task<Response<Request>> Build(
 | 
			
		||||
            string httpMethod, 
 | 
			
		||||
            string downstreamUrl, 
 | 
			
		||||
            Stream content, 
 | 
			
		||||
            IHeaderDictionary headers,
 | 
			
		||||
            IRequestCookieCollection cookies, 
 | 
			
		||||
            QueryString queryString, 
 | 
			
		||||
            string contentType, 
 | 
			
		||||
            RequestId.RequestId requestId)
 | 
			
		||||
        {
 | 
			
		||||
            var request = await new RequestBuilder()
 | 
			
		||||
                .WithHttpMethod(httpMethod)
 | 
			
		||||
                .WithDownstreamUrl(downstreamUrl)
 | 
			
		||||
                .WithQueryString(queryString)
 | 
			
		||||
                .WithContent(content)
 | 
			
		||||
                .WithContentType(contentType)
 | 
			
		||||
                .WithHeaders(headers)
 | 
			
		||||
                .WithRequestId(requestId)
 | 
			
		||||
                .WithCookies(cookies)
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            return new OkResponse<Request>(request);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,7 @@ using Ocelot.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Request.Builder
 | 
			
		||||
{
 | 
			
		||||
    public interface IRequestBuilder
 | 
			
		||||
    public interface IRequestCreator
 | 
			
		||||
    {
 | 
			
		||||
        Task<Response<Request>> Build(string httpMethod,
 | 
			
		||||
            string downstreamUrl,
 | 
			
		||||
							
								
								
									
										183
									
								
								src/Ocelot/Request/Builder/RequestBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/Ocelot/Request/Builder/RequestBuilder.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net.Http.Headers;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.Extensions.Primitives;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Request.Builder
 | 
			
		||||
{
 | 
			
		||||
    internal sealed class RequestBuilder
 | 
			
		||||
    {
 | 
			
		||||
        private HttpMethod _method;
 | 
			
		||||
        private string _downstreamUrl;
 | 
			
		||||
        private QueryString _queryString;
 | 
			
		||||
        private Stream _content;
 | 
			
		||||
        private string _contentType;
 | 
			
		||||
        private IHeaderDictionary _headers;
 | 
			
		||||
        private RequestId.RequestId _requestId;
 | 
			
		||||
        private IRequestCookieCollection _cookies;
 | 
			
		||||
        private readonly string[] _unsupportedHeaders = {"host"};
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithHttpMethod(string httpMethod)
 | 
			
		||||
        {
 | 
			
		||||
            _method = new HttpMethod(httpMethod);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithDownstreamUrl(string downstreamUrl)
 | 
			
		||||
        {
 | 
			
		||||
            _downstreamUrl = downstreamUrl;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithQueryString(QueryString queryString)
 | 
			
		||||
        {
 | 
			
		||||
            _queryString = queryString;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithContent(Stream content)
 | 
			
		||||
        {
 | 
			
		||||
            _content = content;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithContentType(string contentType)
 | 
			
		||||
        {
 | 
			
		||||
            _contentType = contentType;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithHeaders(IHeaderDictionary headers)
 | 
			
		||||
        {
 | 
			
		||||
            _headers = headers;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithRequestId(RequestId.RequestId requestId)
 | 
			
		||||
        {
 | 
			
		||||
            _requestId = requestId;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithCookies(IRequestCookieCollection cookies)
 | 
			
		||||
        {
 | 
			
		||||
            _cookies = cookies;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Request> Build()
 | 
			
		||||
        {
 | 
			
		||||
            var uri = new Uri(string.Format("{0}{1}", _downstreamUrl, _queryString.ToUriComponent()));
 | 
			
		||||
 | 
			
		||||
            var httpRequestMessage = new HttpRequestMessage(_method, uri);
 | 
			
		||||
 | 
			
		||||
            await AddContentToRequest(httpRequestMessage);
 | 
			
		||||
 | 
			
		||||
            AddContentTypeToRequest(httpRequestMessage);
 | 
			
		||||
 | 
			
		||||
            AddHeadersToRequest(httpRequestMessage);
 | 
			
		||||
 | 
			
		||||
            if (RequestIdKeyIsNotNull(_requestId) && !RequestIdInHeaders(_requestId, httpRequestMessage.Headers))
 | 
			
		||||
            {
 | 
			
		||||
                AddRequestIdHeader(_requestId, httpRequestMessage);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cookieContainer = CreateCookieContainer(uri);
 | 
			
		||||
 | 
			
		||||
            return new Request(httpRequestMessage, cookieContainer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task AddContentToRequest(HttpRequestMessage httpRequestMessage)
 | 
			
		||||
        {
 | 
			
		||||
            if (_content != null)
 | 
			
		||||
            {
 | 
			
		||||
                httpRequestMessage.Content = new ByteArrayContent(await ToByteArray(_content));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddContentTypeToRequest(HttpRequestMessage httpRequestMessage)
 | 
			
		||||
        {
 | 
			
		||||
            if (!string.IsNullOrEmpty(_contentType))
 | 
			
		||||
            {
 | 
			
		||||
                httpRequestMessage.Content.Headers.Remove("Content-Type");
 | 
			
		||||
                httpRequestMessage.Content.Headers.TryAddWithoutValidation("Content-Type", _contentType);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddHeadersToRequest(HttpRequestMessage httpRequestMessage)
 | 
			
		||||
        {
 | 
			
		||||
            if (_headers != null)
 | 
			
		||||
            {
 | 
			
		||||
                _headers.Remove("Content-Type");
 | 
			
		||||
 | 
			
		||||
                foreach (var header in _headers)
 | 
			
		||||
                {
 | 
			
		||||
                    //todo get rid of if..
 | 
			
		||||
                    if (IsSupportedHeader(header))
 | 
			
		||||
                    {
 | 
			
		||||
                        httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool IsSupportedHeader(KeyValuePair<string, StringValues> header)
 | 
			
		||||
        {
 | 
			
		||||
            return !_unsupportedHeaders.Contains(header.Key.ToLower());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private CookieContainer CreateCookieContainer(Uri uri)
 | 
			
		||||
        {
 | 
			
		||||
            var cookieContainer = new CookieContainer();
 | 
			
		||||
 | 
			
		||||
            if (_cookies != null)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var cookie in _cookies)
 | 
			
		||||
                {
 | 
			
		||||
                    cookieContainer.Add(uri, new Cookie(cookie.Key, cookie.Value));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return cookieContainer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddRequestIdHeader(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage)
 | 
			
		||||
        {
 | 
			
		||||
            httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool RequestIdInHeaders(RequestId.RequestId requestId, HttpRequestHeaders headers)
 | 
			
		||||
        {
 | 
			
		||||
            IEnumerable<string> value;
 | 
			
		||||
            if (headers.TryGetValues(requestId.RequestIdKey, out value))
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool RequestIdKeyIsNotNull(RequestId.RequestId requestId)
 | 
			
		||||
        {
 | 
			
		||||
            return !string.IsNullOrEmpty(requestId?.RequestIdKey) && !string.IsNullOrEmpty(requestId.RequestIdValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<byte[]> ToByteArray(Stream stream)
 | 
			
		||||
        {
 | 
			
		||||
            using (stream)
 | 
			
		||||
            {
 | 
			
		||||
                using (var memStream = new MemoryStream())
 | 
			
		||||
                {
 | 
			
		||||
                    await stream.CopyToAsync(memStream);
 | 
			
		||||
                    return memStream.ToArray();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,20 +9,20 @@ namespace Ocelot.Request.Middleware
 | 
			
		||||
    public class HttpRequestBuilderMiddleware : OcelotMiddleware
 | 
			
		||||
    {
 | 
			
		||||
        private readonly RequestDelegate _next;
 | 
			
		||||
        private readonly IRequestBuilder _requestBuilder;
 | 
			
		||||
        private readonly IRequestCreator _requestCreator;
 | 
			
		||||
 | 
			
		||||
        public HttpRequestBuilderMiddleware(RequestDelegate next, 
 | 
			
		||||
            IRequestScopedDataRepository requestScopedDataRepository, 
 | 
			
		||||
            IRequestBuilder requestBuilder)
 | 
			
		||||
            IRequestCreator requestCreator)
 | 
			
		||||
            :base(requestScopedDataRepository)
 | 
			
		||||
        {
 | 
			
		||||
            _next = next;
 | 
			
		||||
            _requestBuilder = requestBuilder;
 | 
			
		||||
            _requestCreator = requestCreator;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            var buildResult = await _requestBuilder
 | 
			
		||||
            var buildResult = await _requestCreator
 | 
			
		||||
                .Build(context.Request.Method, DownstreamUrl, context.Request.Body,
 | 
			
		||||
                    context.Request.Headers, context.Request.Cookies, context.Request.QueryString,
 | 
			
		||||
                    context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier));
 | 
			
		||||
 
 | 
			
		||||
@@ -15,16 +15,16 @@ namespace Ocelot.Responder
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class HttpContextResponder : IHttpResponder
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IRemoveHeaders _removeHeaders;
 | 
			
		||||
        private readonly IRemoveOutputHeaders _removeOutputHeaders;
 | 
			
		||||
 | 
			
		||||
        public HttpContextResponder(IRemoveHeaders removeHeaders)
 | 
			
		||||
        public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders)
 | 
			
		||||
        {
 | 
			
		||||
            _removeHeaders = removeHeaders;
 | 
			
		||||
            _removeOutputHeaders = removeOutputHeaders;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
 | 
			
		||||
        {
 | 
			
		||||
            _removeHeaders.Remove(response.Headers);
 | 
			
		||||
            _removeOutputHeaders.Remove(response.Headers);
 | 
			
		||||
 | 
			
		||||
            foreach (var httpResponseHeader in response.Headers)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user