코딩항해기

[실습/Flutter] inappwebview로 http status 처리 본문

problem solving/과제&실습 코딩

[실습/Flutter] inappwebview로 http status 처리

miniBcake 2025. 5. 21. 11:45

 

flutter에는 webview가 두 가지 있는데 그냥 webview는 공식에서 관리하는 대신 기능이 적고 inappwebview는 서드파티앱인 대신 기능이 상대적으로 많은 webview이다.

 

처음에는 webview를 사용했는데 http status를 받아와서 404나 500처리를 하려니 쉽지 않아서 inappwebview로 전환했다.

뭔가 코드 구조도 좀 더 간결한 느낌...!

 

import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; // InAppWebView 패키지 가져오기

void main() {
  runApp(const Webview()); // 앱 실행
}

class Webview extends StatelessWidget {
  const Webview({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: WebviewPractice(), // 홈 화면으로 WebviewPractice 설정
    );
  }
}

class WebviewPractice extends StatefulWidget {
  const WebviewPractice({super.key});

  @override
  State<WebviewPractice> createState() => _WebviewPracticeState(); // 상태 생성
}

class _WebviewPracticeState extends State<WebviewPractice> {
  late InAppWebViewController _controller; // 웹뷰 컨트롤러 

  // 접근 제어 목록
  final List<String> whitelist = ['naver.com', 'flutter.dev']; // 허용할 도메인 목록
  final List<String> blacklist = ['google.co.kr']; // 차단할 도메인 목록

  // 화이트 리스트 함수
  Future<NavigationActionPolicy> _checkWhiteList (host) async {
    if (whitelist.any((allowed) => host.contains(allowed)))
      return NavigationActionPolicy.ALLOW; // 탐색 허용

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('허용되지 않은 사이트입니다: $host')),
    );
    return NavigationActionPolicy.CANCEL; // 탐색 차단
  }

  // 블랙 리스트 함수
  Future<NavigationActionPolicy> _checkBlackList (host) async {
    if (blacklist.any((blocked) => host.contains(blocked))) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('차단된 사이트입니다: $host')),
      );
      return NavigationActionPolicy.CANCEL; // 탐색 차단
    }
    return NavigationActionPolicy.ALLOW; // 그 외의 모든 URL은 허용
  }

  Future<NavigationActionPolicy> _chooseCheckList(host, String msg) async {
    switch (msg) {
      case 'B':
        return _checkBlackList(host);
      case 'W':
        return _checkWhiteList(host);
      default:
        debugPrint('error: 알 수 없는 리스트 타입 - $msg');
        return NavigationActionPolicy.CANCEL;
    }
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('InAppWebView')), // 앱바 제목

      body: InAppWebView(
        // 초기 URL 설정
        initialUrlRequest: URLRequest(
          url: WebUri('https://naver.com'), // WebUri로 URL 생성 (초기 uri)
          
          // 테스트용
          // url: WebUri('https://httpstat.us/404'), // 404 오류 테스트를 위한 URL
          // url: WebUri('https://httpstat.us/500'), // 500 오류 테스트
          // url: WebUri('https://example.com/nonexistent-page'), // 404 오류 발생
          // url: WebUri('https://thiswebsitedoesnotexist.zzz'), // 도메인 해석 실패 테스트
        ),

        // 웹뷰 초기 설정
        initialSettings: InAppWebViewSettings(
          javaScriptEnabled: true, // 자바스크립트 활성화
        ),

        // 웹뷰가 생성됐을 때 호출되는 콜백
        onWebViewCreated: (controller) => _controller = controller, // 컨트롤러 저장 (나중에 웹뷰 제어에 사용)

        // HTTP 통신 오류 처리 (서버 응답 오류: 404, 500 등)
        onReceivedHttpError: (controller, request, errorResponse) { // 매개변수로 받는 controller는 이벤트 controller, 콜백에서 저장한 _controller는 전역.. (동일한 객체 참조)
          int? statusCode = errorResponse.statusCode; //상태코드
          debugPrint('HTTP error: $statusCode, url: ${request.url}, description: ${errorResponse.reasonPhrase}');

          // 404나 500 오류일 때 리다이렉트 (연습을 위해 다른 페이지로 요청)
          if ( (statusCode == 404 || statusCode == 500) )
            _controller.loadUrl( urlRequest: URLRequest(url: WebUri('https://new.express.adobe.com/')) );
        },

        // 웹뷰 로딩 자체 오류 처리 (네트워크 연결 실패, 도메인 해석 실패 등)
        onReceivedError: (controller, request, error) => debugPrint('Load error: ${error.type.toString()}, ${error.description}, url: ${request.url}'),

        // URL 로딩 요청을 가로채서 허용/차단 결정하는 콜백
        shouldOverrideUrlLoading: (controller, navigationAction) async {
          final uri = navigationAction.request.url;
          if (uri == null) return NavigationActionPolicy.CANCEL; // URL이 없으면 탐색 차단
          final host = uri.host; // 호스트 도메인 추출

          return _chooseCheckList(host, 'B');
        },
      ),
    );
  }
}

 

 

        // HTTP 통신 오류 처리 (서버 응답 오류: 404, 500 등)
        onReceivedHttpError: (controller, request, errorResponse) { // 매개변수로 받는 controller는 이벤트 controller, 콜백에서 저장한 _controller는 전역.. (동일한 객체 참조)
          int? statusCode = errorResponse.statusCode; //상태코드
          debugPrint('HTTP error: $statusCode, url: ${request.url}, description: ${errorResponse.reasonPhrase}');

          // 404나 500 오류일 때 리다이렉트 (연습을 위해 다른 페이지로 요청)
          if ( (statusCode == 404 || statusCode == 500) )
            _controller.loadUrl( urlRequest: URLRequest(url: WebUri('https://new.express.adobe.com/')) );
        },

 

이 부분이 에러 발생 시 리다이렉트하는 부분이며, errorResponse로부터 statusCode를 받아와 전역 controller를 통해 리다이렉트 시키는 구조로 되어있다. onReceiveHttpError의 경우 http 에러가 발생했을 때만 처리되기 때문에 네트워크 문제나 도메인 해석 실패 등은 감지할 수 없고, 정상 작동도 여기서는 처리할 수 없다.