.Net Core 3.0后台使用httpclient请求网络网页和图片_使用Core3.0做一个简单的代理服务器

原文:.Net Core 3.0后台使用httpclient请求网络网页和图片_使用Core3.0做一个简单的代理服务器

目标:使用.net core最新的3.0版本,借助httpclient和本机的host域名代理,实现网络请求转发和内容获取,最终显示到目标客户端!

背景:本人在core领域是个新手,对core的使用不多,因此在实现的过程中遇到了很多坑,在这边博客中,逐一介绍下。下面进入正文

正文:

 

1-启用httpClient注入:

参考文档:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.0

services.AddHttpClient("configured-inner-handler").ConfigurePrimaryHttpMessageHandler(() =>
            {
                return new HttpClientHandler()
                {
                    AllowAutoRedirect = false,
                    UseDefaultCredentials = true,
                    Proxy = new MyProxy(new Uri("你的代理Host"))
                };
            });

这里添加了httpClient的服务,且设置了一些其他选项:代理等

2-添加和配置接受请求的中间件:

参考文档:1:  官网-中间件      2:    ASP.NET到ASP.NET Core Http模块的迁移

a-创建中间件:

public class DomainMappingMiddleware : BaseMiddleware
    {
        public ConfigSetting ConfigSetting { get; set; }

        public ILogger<DomainMappingMiddleware> Logger { get; set; }

        public HttpClient HttpClient = null;

        private static object _Obj = new object();
        public DomainMappingMiddleware(RequestDelegate next, IConfiguration configuration, IMemoryCache memoryCache, ConfigSetting configSetting, ILogger<DomainMappingMiddleware> logger, IHttpClientFactory clientFactory) : base(next, configuration, memoryCache)
        {
            this.ConfigSetting = configSetting;
            this.Logger = logger;
            this.HttpClient = clientFactory.CreateClient("domainServiceClient");
        }

        public async Task Invoke(HttpContext httpContext)
        {
            string requestUrl = null;
            string requestHost = null;

            string dateFlag = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss:fff");

            requestUrl = httpContext.Request.GetDisplayUrl();

            bool isExistDomain = false;
            bool isLocalWebsite = this.ConfigSetting.GetValue("IsLocalDomainService") == "true";

            if (httpContext.Request.Query.ContainsKey("returnurl"))
            {
                requestUrl = httpContext.Request.Query["returnurl"].ToString();
                requestUrl = HttpUtility.UrlDecode(requestUrl);
                isLocalWebsite = false;
            }

            Match match = Regex.Match(requestUrl, this.ConfigSetting.GetValue("DomainHostRegex"));
            if (match.Success)
            {
                isExistDomain = true;
                requestHost = match.Value;
            }

#if DEBUG
            requestUrl = "http://139.199.128.86:444/?returnurl=https%3A%2F%2F3w.huanqiu.com%2Fa%2Fc36dc8%2F9CaKrnKnonm";
#endif

            if (isExistDomain)
            {
                this.Logger.LogInformation($"{dateFlag}_记录请求地址:{requestUrl},是否存在当前域:{isExistDomain},是否是本地环境:{isLocalWebsite}");

                bool isFile = false;

                //1-设置响应的内容类型
                MediaTypeHeaderValue mediaType = null;

                if (requestUrl.Contains(".js"))
                {
                    mediaType = new MediaTypeHeaderValue("application/x-javascript");
                    //mediaType.Encoding = System.Text.Encoding.UTF8;
                }
                else if (requestUrl.Contains(".css"))
                {
                    mediaType = new MediaTypeHeaderValue("text/css");
                    //mediaType.Encoding = System.Text.Encoding.UTF8;
                }
                else if (requestUrl.Contains(".png"))
                {
                    mediaType = new MediaTypeHeaderValue("image/png");
                    isFile = true;
                }
                else if (requestUrl.Contains(".jpg"))
                {
                    mediaType = new MediaTypeHeaderValue("image/jpeg");
                    isFile = true;
                }
                else if (requestUrl.Contains(".ico"))
                {
                    mediaType = new MediaTypeHeaderValue("image/x-icon");
                    isFile = true;
                }
                else if (requestUrl.Contains(".gif"))
                {
                    mediaType = new MediaTypeHeaderValue("image/gif");
                    isFile = true;
                }
                else if (requestUrl.Contains("/api/") && !requestUrl.Contains("/views"))
                {
                    mediaType = new MediaTypeHeaderValue("application/json");
                }
                else
                {
                    mediaType = new MediaTypeHeaderValue("text/html");
                    mediaType.Encoding = System.Text.Encoding.UTF8;
                }

                //2-获取响应结果

                if (isLocalWebsite)
                {
                    //本地服务器将请求转发到远程服务器
                    requestUrl = this.ConfigSetting.GetValue("MyDomainAgentHost") + "?returnurl=" + HttpUtility.UrlEncode(requestUrl);
                }

                if (isFile == false)
                {
                    string result = await this.HttpClient.MyGet(requestUrl);

                    if (httpContext.Response.HasStarted == false)
                    {
                        this.Logger.LogInformation($"{dateFlag}_请求结束_{requestUrl}_长度{result.Length}");

                        //请求结果展示在客户端,需要重新请求本地服务器,因此需要将https转为http
                        result = result.Replace("https://", "http://");
                        //替换"/a.ico" 为:"http://www.baidu.com/a.ico"
                        result = Regex.Replace(result, "\"\\/(?=[a-zA-Z0-9]+)", $"\"{requestHost}/");
                        //替换"//www.baidu.com/a.ico" 为:"http://www.baidu.com/a.ico"
                        result = Regex.Replace(result, "\"\\/\\/(?=[a-zA-Z0-9]+)", "\"http://");

                        //必须有请求结果才能给内容类型赋值;如果请求过程出了异常再赋值,会报错:The response headers cannot be modified because the response has already started.
                        httpContext.Response.ContentType = mediaType.ToString();

                        await httpContext.Response.WriteAsync(result ?? "");
                    }
                    else
                    {
                        this.Logger.LogInformation($"{dateFlag}_请求结束_{requestUrl}_图片字节流长度{result.Length}_Response已启动");
                    }
                }
                else
                {
                    byte[] result = await this.HttpClient.MyGetFile(requestUrl);

                    if (httpContext.Response.HasStarted == false)
                    {
                        this.Logger.LogInformation($"{dateFlag}_请求结束_{requestUrl}_图片字节流长度{result.Length}");

                        httpContext.Response.ContentType = mediaType.ToString();
                        await httpContext.Response.Body.WriteAsync(result, 0, result.Length);
                    }
                    else
                    {
                        this.Logger.LogInformation($"{dateFlag}_请求结束_{requestUrl}_图片字节流长度{result.Length}_Response已启动");
                    }
                }
            }
        }
    }

继承类:

/// <summary>
    /// 中间件基类
    /// </summary>
    public abstract class BaseMiddleware
    {
        /// <summary>
        /// 等同于ASP.NET里面的WebCache(HttpRuntime.Cache)
        /// </summary>
        protected IMemoryCache MemoryCache { get; set; }

        /// <summary>
        /// 获取配置文件里面的配置内容
        /// </summary>
        protected IConfiguration Configuration { get; set; }

        public BaseMiddleware(RequestDelegate next, params object[] @params)
        {
            foreach (var item in @params)
            {
                if (item is IMemoryCache)
                {
                    this.MemoryCache = (IMemoryCache)item;
                }
                else if (item is IConfiguration)
                {
                    this.Configuration = (IConfiguration)item;
                }
            }
        }

    }

httpClient扩展类:

public static class HttpClientSingleston
    {
        public async static Task<string> MyGet(this HttpClient httpClient, string url)
        {
            string result = null;

            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
            {
                using (var response = await httpClient.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        using (Stream stream = await response.Content.ReadAsStreamAsync())
                        {
                            using (StreamReader streamReader = new StreamReader(stream, Encoding.UTF8))
                            {
                                result = await streamReader.ReadToEndAsync();
                            }
                        }

                    }
                }
            }
            return result ?? "";
        }

        public async static Task<byte[]> MyGetFile(this HttpClient httpClient, string url)
        {
            byte[] result = null;
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
            {
                using (var response = await httpClient.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        result = await response.Content.ReadAsByteArrayAsync();
                    }
                }
            }
            return result ?? new byte[0];
        }

    }

b-注册中间件:

在Startup.cs的Configure方法中:

app.UseMiddleware<DomainMappingMiddleware>();

小结:该中间件负责接受请求,并处理请求(由于项目是用来专门处理网络网页和图片的,因此没有对请求的Url筛选过滤,实际使用时需要注意);该中间件即负责处理请求的转发,又负责处理网络图片和内容的获取;

转发的目的,当然是为了规避网络IP的限制,当你想访问某一网站却发现被禁止访问的时候,而这时候你又有一台可以正常访问的服务器且你和你的服务器能正常连接的时候,那么你就可以用这个方式了,做一个简单的代理服务器做中转,来间接访问我们想看的网站,是不是很神奇?  哈哈,我觉得是的,因为没这么干过。

踩过的坑有:

  bug0-HTTP Error 500.0 - ANCM In-Process Handler Load Failure

  bug1-The response headers cannot be modified because the response has already started.

  bug2-An unhandled exception was thrown by the application. IFeatureCollection has been disposed

  bug3-An unhandled exception was thrown by the application. The SSL connection could not be established, see inner exception.

  bug4-this request has no response data

  bug5-获取的网络图片返回字符串乱码

  bug6-浏览器显示网页各种资源请求错误:IIS7.5 500 Internal Server Error

  bug7-response如何添加响应头?

  bug8-如何设置在core中设置服务器允许跨域请求?

  bug9-如何在Core中使用NLog日志记录请求信息和错误?

逐一解答:

  bug0:一般会在第一次在IIS上调试core项目会遇到,一般是因为电脑未安装AspNetCoreModuleV2对IIS支持Core的模块导致,还需要检查项目的应用程序池的.Net Framework版本是否是选择的无托管模式。

  

参考其他道友文章:https://www.cnblogs.com/leoxjy/p/10282148.html

  

  bug1:这是因为response发送响应消息后,又修改了response的头部的值抛出的异常,我上面列举的代码已经处理了该问题,该问题导致了我的大部分坑的产生,也是我遇到的最大的主要问题。这个错误描述很清楚,但是我从始至终的写法并没有在response写入消息后,又修改response的头部,且为了修改该问题,使用了很多辅助手段:

  在发送消息前使用:if (httpContext.Response.HasStarted == false) 做判断后再发送,结果是错误少了一些,但是还是有的,后来怀疑是多线程可能导致的问题,我又加上了了lock锁,使用lock锁和response的状态一起判断使用,最后是堵住了该错误,但是我想要的内容并没有出现,且浏览器端显示了很多bug6错误。

  

  最后是在解决bug2的时候,终于在google上搜索到正确的答案:Disposed IFeatureCollection for subsequent API requests    通过左边的文档找到了关键的开发指南: ASP.NET核心指南

  通过指南发现我的一个严重错误:

    a-将httpContext及其属性(request,response等)存到了中间件的属性中使用!!!    X

    b-将httpContext及其属性(request,response等)存到了中间件的属性中使用!!!    XX

    c-将httpContext及其属性(request,response等)存到了中间件的属性中使用!!!    XXX

  这个我自己挖的深坑导致我很多的错误!

  不让这样用的原因主要是以为Core的特性,没错,就是注入,其中中间件是一个注入进来的单例模式的类,在启动后会初始化一次构造函数,但是之后的请求就不会再执行了,因此如果把context放到单例的属性中,结果可想而知,单例的属性在多线程下,数据不乱才改,response在发送消息后不被再次修改才怪!!

  

  bug2:同bug1.

  bug3:不记得怎么处理的了,可能和权限和https请求有关,遇到在修改解决方案吧,大家也可以百度和谷歌,是能搜到的,能不能解决问题,大家去试吧。

  bug4:是请求没有响应的意思,这里是我在获取内容的时候使用的异步方法,没有使用await等待结果导致的。一般使用httpClient获取影响内容要加上:await httpClient.SendAsync(request) ,等待结果后再做下一步处理。

  bug5:获取响应的图片乱码是困扰我的另一个主要问题:

    初步的实现方式是:请求图片地址,获取响应字符,直接返回给客户端,这肯定不行。因为你需要在response的内容类型上加上对应的类型值:

      mediaType = new MediaTypeHeaderValue("image/jpeg");

      httpContext.Response.ContentType = mediaType.ToString();

      await httpContext.Response.WriteAsync(result ?? "")

    蓝后,上面虽然加了响应的内容类型依然不行,因为图片是一种特殊的数据流,不能简单实用字符串传输的方式,字节数据在转换的过程中可能丢失。后来在领导的项目中看到了以下发送图片响应的方法:

//直接输出文件
await response.SendFileAsync(physicalFileInfo);

    尝试后发现,我只能将response的响应内容读取中字符串,怎么直接转成图片文件呢?  难道我要先存下来,再通过这种方式发送出去,哎呀!物理空间有限啊,不能这么干,必须另想他发,百度和google搜索后都没有找到解决方案,终于想了好久,突然发现Response对象的Body属性是一个Stream类型,是可以直接出入字节数据的,于是最终的解决方案出炉啦:

    本解决方案独一无二,百度谷歌独家一份,看到就是赚到哈!!!

    一段神奇的代码产生了:await httpContext.Response.Body.WriteAsync(result, 0, result.Length);

public async static Task<byte[]> MyGetFile(this HttpClient httpClient, string url)
        {
            byte[] result = null;
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
            {
                using (var response = await httpClient.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        result = await response.Content.ReadAsByteArrayAsync();
                    }
                }
            }
            return result ?? new byte[0];
        }

byte[] result = await this.HttpClient.MyGetFile(requestUrl);

                    if (httpContext.Response.HasStarted == false)
                    {
                        this.Logger.LogInformation($"{dateFlag}_请求结束_{requestUrl}_图片字节流长度{result.Length}");
                        MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("image/gif");
                        httpContext.Response.ContentType = mediaType.ToString();
                        await httpContext.Response.Body.WriteAsync(result, 0, result.Length);
                    }
                    else
                    {
                        this.Logger.LogInformation($"{dateFlag}_请求结束_{requestUrl}_图片字节流长度{result.Length}_Response已启动");
                    }

  bug6:同bug1.

  bug7:官网文档给了解决方案,总之就是,你不要在response写入消息后再修改response就好了。    参照官方文档:  发送HttpContext.Response.Headers

  

  bug8:直接上代码吧:

    在Setup.cs的ConfigService方法中添加:

services.AddCors(options =>
            {
                options.AddPolicy("AllowSameDomain", builder =>
                {
                    //允许任何来源的主机访问
                    builder.AllowAnyOrigin()
                    .AllowAnyHeader();
                });
            });

    在Setup.cs的Configure方法中添加:

app.UseCors();

  bug9:使用NLog日志的代码如下:

    在Program.cs其中类的方法CreateHostBuilder添加以下加粗代码:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).ConfigureLogging(logging =>
                {
                    //https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-3
                    logging.ClearProviders();
                    logging.SetMinimumLevel(LogLevel.Information);
                }).UseNLog();

    添加Nlog的配置文件:nlog.config

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Warn"
      internalLogFile="internal-nlog.txt">

  <!--define various log targets-->
  <targets>
    <!--write logs to file-->
    <target xsi:type="File" name="allfile" fileName="${basedir}/logs/${shortdate}.log"
                 layout="${longdate}|${logger}|${uppercase:${level}}${newline}${message} ${exception}${newline}" />

    <target xsi:type="Console" name="console"
          layout= "${longdate}|${logger}|${uppercase:${level}}${newline}${message} ${exception}${newline}"/>
  </targets>
  <rules>
    <!--All logs, including from Microsoft-->
    <!--<logger name="*" minlevel="Trace" writeTo="allfile" />-->
    <!--Skip Microsoft logs and so log only own logs-->
    <logger name="*" minlevel="Info" writeTo="allfile" />
  </rules>
</nlog>

  最后是给项目注入NLog的Nuget核心包引用:

  

  使用方式是注入的方式:

public ILogger<DomainMappingMiddleware> Logger { get; set; }

        public HttpClient HttpClient = null;

        private static object _Obj = new object();
        public DomainMappingMiddleware(RequestDelegate next, IConfiguration configuration, IMemoryCache memoryCache, ConfigSetting configSetting, ILogger<DomainMappingMiddleware> logger, IHttpClientFactory clientFactory) : base(next, configuration, memoryCache)
        {
            this.ConfigSetting = configSetting;
            this.Logger = logger;
            this.HttpClient = clientFactory.CreateClient("domainServiceClient");
        }

this.Logger.LogInformation($"{dateFlag}_记录请求地址:{requestUrl},是否存在当前域:{isExistDomain},是否是本地环境:{isLocalWebsite}");

  3-坑说完了,最后说说怎么绕过IP限制吧:

    首先我们需要将https请求改成http请求,当然如果你的IIS支持Https可以不改;然后你需要修改本机的Host域名解析规则,将你要绕的域指向本机IIS服务器:127.0.0.1,不知道的小伙伴可以百度怎么修改本机域名解析;

    

    IIS接收到请求后,你还需要在项目中加上域名配置,端口号一定是80哦:

    

    应用程序池配置:

    

    这样就实现了将网络请求转到IIS中了,那么通过IIS部署的项目接收后,使用Core3.0最新的httpClient技术将请求转发到你的服务器中,当然你的服务器也需要一个项目来接收发来的请求;

    最后是通过服务器项目发送网络请求到目标网站请求真正的内容,最后再依次返回给用户,也就是我们的浏览器,进行展示。。。

    结束了。。。写了2个小时的博客,有点累,欢迎大家留言讨论哈,不足之处欢迎指教!

原文地址:https://www.cnblogs.com/lonelyxmas/p/12065758.html

时间: 2024-12-28 01:38:25

.Net Core 3.0后台使用httpclient请求网络网页和图片_使用Core3.0做一个简单的代理服务器的相关文章

listview+BaseAdapter + AsyncTask异步请求网络 + LruCache缓存图片

1,通过异步加载,避免阻塞UI线程 2,通过LruCache,将已经下载的图片放到内存中 3,通过判断Listview滑动状态,决定何时加载图片 4,不仅仅是listview ,任何控件都可以使用异步加载 代码结构: 布局1, <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools&qu

HttpClient请求网络数据的Post请求

new Thread(){            public void run() {                                try { //获得输入框内容                    String phonenum=et_phone_num.getText().toString().trim();                    String password=et_password.getText().toString().trim();      

cocos2d-x 3.0来做一个简单的游戏教程 win32平台 vs2012 详解献给刚開始学习的人们!

原代码来自于网络,因为cocos2d-x 3.0的资料,的确不多,与曾经版本号的接口非常难对上, 所以网上非常多样例都无法调试,对于新学习cocos2d-x 的同学,难度添加了,所以出一个超具体的样例给大家. 源代码地址:http://download.csdn.net/detail/adady/7293629 #include "HelloWorldScene.h" #include "SimpleAudioEngine.h" USING_NS_CC; Scene

as3.0加载本地或网络上的图片

加载本地或网络上的图片,我们一般只用Loader及URLRequest这两个类就可以完成,URLRequest即可以加载本地的,也可以加载网络的.代码如下 import flash.display.Loader; import flash.net.URLRequest; var loader:Loader = new Loader(); var request:URLRequest = new URLRequest('img/123.png'); loader.y = 200; loader.l

Android 手机卫士--构建服务端json、请求网络数据

本文地址:http://www.cnblogs.com/wuyudong/p/5900384.html,转载请注明源地址. 数据的传递 客户端:发送http请求 http://www.oxx.com/index.jsp?key=value 服务器:在接受到请求以后,给客户端发送数据,(json,xml),json数据从数据库中读取出来,读取数据拼接json,语法规则,结构 获取服务器版本号(客户端发请求,服务端给响应,(json,xml)) http://www.oxxx.com/update.

获取后台富文本框内容,截取图片

1.split()  分割字符串,转化成数组 (1)分割数据中有某段字符串的数据 ,转化成数组 //拿取富文本框中的图片var div=data[0].text.split("/agriculture/uploads/");//定义一个变量存放字符串数据 var divstr="";//循环div的长度 for(var k=0;k<div.length;k++){ if(k==0){ //第一个k是字符串,文本内容 divstr+=div[k]; }else{

Windows 上静态编译 Libevent 2.0.10 并实现一个简单 HTTP 服务器(无数截图)

[文章作者:张宴 本文版本:v1.0 最后修改:2011.03.30 转载请注明原文链接:http://blog.s135.com/libevent_windows/] 本文介绍了如何在 Windows 操作系统中,利用微软 Visual Studio 2005 编译生成 Libevent 2.0.10 静态链接库,并利用 Libevent 静态链接库,实现一个简单的 HTTP Web服务器程序:httpd.exe. 假设 Visual Studio 2005 的安装路径为“D:\Program

Windows 上静态编译 Libevent 2.0.10 并实现一个简单 HTTP 服务器(图文并茂,还有实例下载)

[文章作者:张宴 本文版本:v1.0 最后修改:2011.03.30 转载请注明原文链接:http://blog.s135.com/libevent_windows/] 本文介绍了如何在 Windows 操作系统中,利用微软 Visual Studio 2005 编译生成 Libevent 2.0.10 静态链接库,并利用 Libevent 静态链接库,实现一个简单的 HTTP Web服务器程序:httpd.exe. 假设 Visual Studio 2005 的安装路径为“D:\Program

Vue2.0基础学习(3)--- 一个简单的实例学习

看完vue 的官方文档,再做一个简单的实例是最好不过了,既能巩固我们所学的知识,又能学以致用.infoq上推荐了一篇文章,面向重度 jQuery 开发者的 Vue.js 介绍, 它是老外写的,用vue做了一个简单的实例,非常适合学完vue文档来练练手,我这里并没有翻译文档,而是做了几次后,自已的思路. 首先看一下这个实例长什么样子,有什么功能 上面是一个文本框,用于输入内容,但最多只能输入140个字,所以右下角会有字数提示.当用户进行输入的时候,右下角的数字不断变化,提示用户还剩多少字可以输入.