티스토리 뷰



앞서서 포스트한 Swift 에서의 설계는 흡사하다.

차이점은 플랫폼이 Android 라는것, 사용된 언어가 C#이라는 것, Alamofire 대신 OkHttp3를 사용했다는 것 뿐이다.

자세한 설명은 해당 포스트를 참고하면 이해에 도움이 될 것이다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Webkit;

using LayoutParams = Android.Views.ViewGroup.LayoutParams;

namespace RhythmGayM
{
    [Activity(Label = "CFDDoSWebViewActivity", Theme = "@android:style/Theme.DeviceDefault", Icon = "@mipmap/icon", ScreenOrientation = Android.Content.PM.ScreenOrientation.Portrait)]
    public class CFDDoSWebViewActivity : Activity
    {
        private readonly Lazy<WebView> webViewMain;

        public CFDDoSWebViewActivity()
        {
            this.webViewMain = new Lazy<WebView>(() => new WebView(this));
        }

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            this.webViewMain.Value.LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
            this.SetContentView(this.webViewMain.Value);

            if ((int)Build.VERSION.SdkInt < 21)
                CookieManager.Instance.RemoveAllCookie();
            else
                CookieManager.Instance.RemoveAllCookies(null);

            var openURL = this.Intent.GetStringExtra("openURL");
            var userAgent = this.Intent.GetStringExtra("userAgent");
            var client = new CFWebViewClient();
            client.completion = delegate (string cookies) {
                Intent intent = new Intent("kr.lonelie.CFDDoSWebViewActivity");
                intent.PutExtra("cookies", cookies);
                this.SendBroadcast(intent);
                this.Finish();
            };
            this.webViewMain.Value.Settings.UserAgentString = userAgent;
            this.webViewMain.Value.SetWebViewClient(client);
            this.webViewMain.Value.Settings.JavaScriptEnabled = true;
            this.webViewMain.Value.LoadUrl(openURL);

            this.Title = "Cloudflare";
        }

        public static void show(string url, string userAgent, Action<string> completion)
        {
            var cfDDoSIntent = new Android.Content.Intent(Application.Context, typeof(CFDDoSWebViewActivity));
            cfDDoSIntent.PutExtra("openURL", url);
            cfDDoSIntent.PutExtra("userAgent", userAgent);

            IntentFilter filter = new IntentFilter();
            CFBroadcastReceiver receiver = new CFBroadcastReceiver();
            receiver.completion = completion;
            filter.AddAction("kr.lonelie.CFDDoSWebViewActivity");
            Application.Context.RegisterReceiver(receiver, filter);
            cfDDoSIntent.SetFlags(ActivityFlags.NewTask);
            Application.Context.StartActivity(cfDDoSIntent);
        }
    }
   
    public class CFBroadcastReceiver : BroadcastReceiver {
        public Action<string> completion;

        public override void OnReceive(Context context, Intent intent)
        {
            //throw new NotImplementedException();
            if (intent.Action.Equals("kr.lonelie.CFDDoSWebViewActivity"))
            {
                string cookies = intent.GetStringExtra("cookies");
                Console.WriteLine("CF-WAF COOKIE: {0}", cookies);
                if (this.completion != null) this.completion(cookies);
            }
        }
    }

    public class CFWebViewClient : WebViewClient
    {
        public Action<string> completion;

        public override void OnPageStarted(WebView view, string url, Android.Graphics.Bitmap favicon)
        {
            string cookies = CookieManager.Instance.GetCookie(url) ?? "";
            bool isFindCookieCFDUID = cookies.Contains("__cfduid=");
            bool isFindCookieCFClearance = cookies.Contains("cf_clearance=");
            if (isFindCookieCFDUID && isFindCookieCFClearance)
            {
                view.StopLoading();
                if (this.completion != null) completion(cookies);
            }
        }
    }
}

이전 Swift의 구현과 차이점이라면 Activity를 시작할 때 값을 넘겨주기 위해 Intent의 putExtra를 사용하는데, 여기에 람다대수(또는 블럭코드, 델리게이트) 를 전달할 수 없다.

때문에 작업 완료 시점을 콜백 받기 위해 브로드캐스트리시버를 사용했다. 그 외의 동작 방식은 Swift와 대동소이하다.

                        string contentType = response.Headers().Values("content-type")[0];
                        if (contentType.Contains("text/html"))
                        { // Rejected request from DDoS protection in CF-WAF.
                            Console.WriteLine("DETECT PROTECT DDOS IN CF-WAF.");
                            CFDDoSWebViewActivity.show(urlString, CoreClient.userAgent, (cookies) => {
                                CoreClient.cfCookies = cookies;
                                CoreClient.request(method, resourcePath, parameters, closure);
                            });
                        }
                        else
                        { // OK.
                            /* blah blah */
                        }

이 부분은 Swift의 구현과 상당히 유사해 보이는 것을 알 수 있을 것이다.

Alamofire는 Iterable<HTTPCookie> 이고 HTTPCookie는 이름, 값, URL, 유효시간 등을 모두 가지는 객체인 반면,

OkHttp3 에서는 StringLiteral 이다. 요청시 Header라는 Dictionary<string, string>에 "Cookie" 라는 이름으로 추가한 후 요청하면 된다.


여기까지 진행되면 API 통신은 문제없이 동작할 것이다.

            OkHttpClient client = new OkHttpClient.Builder()
                                                  .ConnectTimeout(10, TimeUnit.Seconds)
                                                  .ReadTimeout(0, TimeUnit.Seconds)
                                                  .WriteTimeout(0, TimeUnit.Seconds)
                                                  .Build();
            Request request = new Request.Builder()
                                         .Url(this.chatServerURL)
                                         .AddHeader("token", UserInfo.token)
                                         .AddHeader("secret", UserInfo.tokenSecret)
                                         .AddHeader("Cookie", CoreClient.cfCookies)
                                         .AddHeader("User-Agent", CoreClient.userAgent)
                                         .Build();

다음으로 OkHttp3.WS 를 사용하여 연결 요청을 할 때, 리퀘스트 빌더에 "Cookie" 와 "User-Agent" 헤더를 추가하면 웹소켓 연결도 문제없이 된다.

이 부분은 Swift의 Starscream에서 사용한 방법과 매우 유사했다.

            Console.WriteLine("Downloading: {0}", url);

            if (!isCached)
            {
                try
                {
                    using (var webClient = new WebClient())
                    {
                        webClient.Headers.Add("User-Agent", CoreClient.userAgent);
                        webClient.Headers.Add("Cookie", CoreClient.cfCookies);
                        imageBytes = webClient.DownloadData(url);
                        Console.WriteLine("Download complete: {0} ({1} bytes).", url, imageBytes.Length);

                        if (imageBytes != null)
                            caches[url] = imageBytes;
                    }
                }
                catch (Exception error)
                {
                    Console.WriteLine("Download image failed: {0} -> {1}", url, error);
                }
            }
            else
                Console.WriteLine("Read cache: {0} ({1} bytes).", url, imageBytes.Length);

개인적으로 구현하여 사용중인 이미지 리소스 다운로드 코드이다.

WebClient 를 인스턴스하여 사용하고 있고, VS 솔루션 설정에서 AndroidClientHandlerNative TLS 1.2+ 구현을 사용하도록 설정했다.

Swift 에서는 URLRequest를 사용하여 HTTPCookieStorage.shared에 들어있는 쿠키를 자동으로 핸들링 했었지만, WebClient는 위 코드처럼 추가해 주었다.

이 부분에 있어서도 세부적인 부분이 조금씩 다를 뿐 맥락은 Swift의 동작과 흡사하다고 보면 된다.


여기까지 진행하여 실제 스토어에서 판매중인 내 프로젝트의 iOS앱과 Android앱에서 동일한 동작을 구현하였다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/02   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29
글 보관함