Mendapatkan Data Sabun RAW dari Klien Referensi Web yang berjalan di ASP.net

95

Saya mencoba memecahkan masalah klien layanan web dalam proyek saya saat ini. Saya tidak yakin dengan platform Server Layanan (Kemungkinan besar LAMP). Saya yakin ada kesalahan di pihak mereka karena saya telah menghilangkan potensi masalah dengan klien saya. Klien adalah proxy referensi web jenis ASMX standar yang dibuat secara otomatis dari layanan WSDL.

Yang perlu saya dapatkan adalah Pesan SOAP RAW (Permintaan dan Tanggapan)

Apa cara terbaik untuk masalah ini?

Andrew Harry
sumber

Jawaban:

135

Saya membuat perubahan berikut web.configuntuk mendapatkan Amplop SOAP (Request / Response). Ini akan menampilkan semua informasi SOAP mentah ke file trace.log.

<system.diagnostics>
  <trace autoflush="true"/>
  <sources>
    <source name="System.Net" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
    <source name="System.Net.Sockets" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener"
      initializeData="trace.log"/>
  </sharedListeners>
  <switches>
    <add name="System.Net" value="Verbose"/>
    <add name="System.Net.Sockets" value="Verbose"/>
  </switches>
</system.diagnostics>
Keltex
sumber
2
Ini tidak berfungsi untuk saya di 02/2012 menggunakan VS 2010. Adakah yang tahu solusi yang lebih mutakhir?
qxotk
2
Ini membantu. Namun, dalam kasus saya, responsnya adalah Gzip dan menarik ASCII atau kode hex dari output gabungan sangat merepotkan. Apakah metode keluaran alternatif mereka hanya berupa biner atau teks?
2
@Dediqated - Anda dapat menyetel jalur ke file trace.log dengan menyesuaikan nilai atribut initializeData pada contoh di atas.
DougCouto
3
Tidak ada gunanya lagi, saya menggunakan wireshark untuk melihat data SOAP mentah. Tapi Terimakasih!
Dediqated
7
Baik. Tetapi XML untuk saya seperti: System.Net Verbose: 0: [14088] 00000000: 3C 3F 78 6D 6C 20 76 65-72 73 69 6F 6E 3D 22 31: <? Xml version = "1 System.Net Verbose: 0: [14088] 00000010: 2E 30 22 20 65 6E 63 6F-64 69 6E 67 3D 22 75 74: .0 "encoding =" ut ....... Tahu bagaimana memformatnya atau hanya memiliki data xml .?
Habeeb
35

Anda dapat menerapkan SoapExtension yang mencatat permintaan dan respons lengkap ke file log. Anda kemudian dapat mengaktifkan SoapExtension di web.config, yang memudahkan untuk mengaktifkan / menonaktifkan untuk tujuan debugging. Berikut adalah contoh yang telah saya temukan dan modifikasi untuk saya gunakan sendiri, dalam kasus saya logging dilakukan oleh log4net tetapi Anda dapat mengganti metode log dengan milik Anda sendiri.

public class SoapLoggerExtension : SoapExtension
{
    private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
    private Stream oldStream;
    private Stream newStream;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }

    public override object GetInitializer(Type serviceType)
    {
        return null;
    }

    public override void Initialize(object initializer)
    {

    }

    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        oldStream = stream;
        newStream = new MemoryStream();
        return newStream;
    }

    public override void ProcessMessage(SoapMessage message)
    {

        switch (message.Stage)
        {
            case SoapMessageStage.BeforeSerialize:
                break;
            case SoapMessageStage.AfterSerialize:
                Log(message, "AfterSerialize");
                    CopyStream(newStream, oldStream);
                    newStream.Position = 0;
                break;
                case SoapMessageStage.BeforeDeserialize:
                    CopyStream(oldStream, newStream);
                    Log(message, "BeforeDeserialize");
                break;
            case SoapMessageStage.AfterDeserialize:
                break;
        }
    }

    public void Log(SoapMessage message, string stage)
    {

        newStream.Position = 0;
        string contents = (message is SoapServerMessage) ? "SoapRequest " : "SoapResponse ";
        contents += stage + ";";

        StreamReader reader = new StreamReader(newStream);

        contents += reader.ReadToEnd();

        newStream.Position = 0;

        log.Debug(contents);
    }

    void ReturnStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    void ReceiveStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    public void ReverseIncomingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseOutgoingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseStream(Stream stream)
    {
        TextReader tr = new StreamReader(stream);
        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);

        TextWriter tw = new StreamWriter(stream);
        stream.Position = 0;
        tw.Write(strReversed);
        tw.Flush();
    }
    void CopyAndReverse(Stream from, Stream to)
    {
        TextReader tr = new StreamReader(from);
        TextWriter tw = new StreamWriter(to);

        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);
        tw.Write(strReversed);
        tw.Flush();
    }

    private void CopyStream(Stream fromStream, Stream toStream)
    {
        try
        {
            StreamReader sr = new StreamReader(fromStream);
            StreamWriter sw = new StreamWriter(toStream);
            sw.WriteLine(sr.ReadToEnd());
            sw.Flush();
        }
        catch (Exception ex)
        {
            string message = String.Format("CopyStream failed because: {0}", ex.Message);
            log.Error(message, ex);
        }
    }
}

[AttributeUsage(AttributeTargets.Method)]
public class SoapLoggerExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1; 

    public override int Priority
    {
        get { return priority; }
        set { priority = value; }
    }

    public override System.Type ExtensionType
    {
        get { return typeof (SoapLoggerExtension); }
    }
}

Anda kemudian menambahkan bagian berikut ke web.config Anda di mana YourNamespace dan YourAssembly menunjuk ke kelas dan perakitan SoapExtension Anda:

<webServices>
  <soapExtensionTypes>
    <add type="YourNamespace.SoapLoggerExtension, YourAssembly" 
       priority="1" group="0" />
  </soapExtensionTypes>
</webServices>
John Lemp
sumber
Saya akan mencobanya. Saya telah melihat perpanjangan sabun, tetapi bagi saya sepertinya itu lebih untuk hosting layanan. Akan memberi Anda centang jika berhasil :)
Andrew Harry
Ya, ini akan berfungsi untuk mencatat panggilan yang dibuat dari aplikasi web Anda melalui proxy yang dibuat.
John Lemp
Ini adalah rute yang saya gunakan, ini bekerja dengan sangat baik, dan saya dapat menggunakan xpath untuk menutupi data sensitif sebelum masuk.
Dave Baghdanov
Saya perlu menambahkan header ke permintaan sabun di klien. Ini membantu saya. rhyous.com/2015/04/29/…
Rhyous
Saya juga baru mengenal c # dan tidak yakin apa yang harus dimasukkan untuk nama Assembly. Ekstensi tampaknya tidak berjalan.
Collin Anderson
28

Tidak yakin mengapa semua repot dengan web.config atau kelas serializer. Kode di bawah ini berfungsi untuk saya:

XmlSerializer xmlSerializer = new XmlSerializer(myEnvelope.GetType());

using (StringWriter textWriter = new StringWriter())
{
    xmlSerializer.Serialize(textWriter, myEnvelope);
    return textWriter.ToString();
}
Luar biasa
sumber
17
Dari mana myEnvelopeasalnya
ProfK
6
Saya tidak mengerti mengapa jawaban ini tidak memiliki lebih banyak suara positif, langsung dan langsung! Sudah selesai dilakukan dengan baik!
Batista
1
myEnvalope akan menjadi objek yang Anda kirimkan melalui layanan web. Saya tidak bisa mendapatkan ini berfungsi seperti yang dijelaskan (terutama fungsi textWriter.tostring) tetapi bekerja cukup baik untuk tujuan saya dalam mode debug di mana Anda dapat melihat xml mentah setelah Anda memanggil serialize. Sangat menghargai jawaban ini, karena beberapa yang lain telah bekerja dengan hasil yang beragam (pencatatan menggunakan web.config hanya mengembalikan sebagian dari xml misalnya, dan juga tidak mudah untuk diurai).
pengguna2366842
@Bimmerbound: apakah ini akan berfungsi dengan pesan sabun aman yang dikirim melalui VPN terenkripsi?
Our Man in Bananas
6
Jawaban ini tidak menghasilkan amplop sabun, hanya bersambung ke xml. Bagaimana cara mendapatkan permintaan amplop sabun?
Ali Karaca
21

Coba Fiddler2 ini akan membiarkan Anda memeriksa permintaan dan respon. Perlu dicatat bahwa Fiddler bekerja dengan lalu lintas http dan https.

Aaron Fischer
sumber
Ini adalah layanan terenkripsi https
Andrew Harry
8
Fiddler dapat membatalkan enkripsi lalu lintas https
Aaron Fischer
1
Saya menemukan solusi ini lebih baik daripada menambahkan penelusuran ke web / app.config. Terima kasih!
andyuk
1
Tetapi menggunakan system.diagnostics dalam file konfigurasi tidak memerlukan aplikasi pihak ketiga untuk diinstal
Azat
1
@AaronFischer: Apakah Fiddler akan bekerja dengan pesan sabun yang dikirim melalui VPN terenkripsi?
Our Man in Bananas
6

Sepertinya solusi Tim Carter tidak berfungsi jika panggilan ke referensi web melontarkan pengecualian. Saya telah mencoba untuk mendapatkan jawaban web mentah sehingga saya dapat memeriksanya (dalam kode) di penangan kesalahan setelah pengecualian dilemparkan. Namun, saya menemukan bahwa log respons yang ditulis oleh metode Tim kosong saat panggilan melontarkan pengecualian. Saya tidak sepenuhnya memahami kodenya, tetapi tampaknya metode Tim memotong proses setelah titik di mana .Net telah membatalkan dan membuang respons web.

Saya bekerja dengan klien yang mengembangkan layanan web secara manual dengan pengkodean tingkat rendah. Pada titik ini, mereka menambahkan pesan kesalahan proses internal mereka sendiri sebagai pesan berformat HTML ke dalam tanggapan SEBELUM tanggapan yang diformat SOAP. Tentu saja, referensi web .Net automagic meledak dalam hal ini. Jika saya bisa mendapatkan respons HTTP mentah setelah pengecualian dilemparkan, saya dapat mencari dan mengurai respons SOAP apa pun dalam respons HTTP campuran yang kembali dan tahu bahwa mereka menerima data saya dengan baik atau tidak.

Nanti ...

Berikut adalah solusi yang berhasil, bahkan setelah eksekusi (perhatikan bahwa saya hanya setelah respons - bisa mendapatkan permintaan juga):

namespace ChuckBevitt
{
    class GetRawResponseSoapExtension : SoapExtension
    {
        //must override these three methods
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }
        public override object GetInitializer(Type serviceType)
        {
            return null;
        }
        public override void Initialize(object initializer)
        {
        }

        private bool IsResponse = false;

        public override void ProcessMessage(SoapMessage message)
        {
            //Note that ProcessMessage gets called AFTER ChainStream.
            //That's why I'm looking for AfterSerialize, rather than BeforeDeserialize
            if (message.Stage == SoapMessageStage.AfterSerialize)
                IsResponse = true;
            else
                IsResponse = false;
        }

        public override Stream ChainStream(Stream stream)
        {
            if (IsResponse)
            {
                StreamReader sr = new StreamReader(stream);
                string response = sr.ReadToEnd();
                sr.Close();
                sr.Dispose();

                File.WriteAllText(@"C:\test.txt", response);

                byte[] ResponseBytes = Encoding.ASCII.GetBytes(response);
                MemoryStream ms = new MemoryStream(ResponseBytes);
                return ms;

            }
            else
                return stream;
        }
    }
}

Berikut cara Anda mengkonfigurasinya di file konfigurasi:

<configuration>
     ...
  <system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="ChuckBevitt.GetRawResponseSoapExtension, TestCallWebService"
           priority="1" group="0" />
      </soapExtensionTypes>
    </webServices>
  </system.web>
</configuration>

"TestCallWebService" harus diganti dengan nama pustaka (yang kebetulan adalah nama aplikasi konsol pengujian yang saya gunakan).

Anda benar-benar tidak harus pergi ke ChainStream; Anda seharusnya dapat melakukannya dengan lebih mudah dari ProcessMessage sebagai:

public override void ProcessMessage(SoapMessage message)
{
    if (message.Stage == SoapMessageStage.BeforeDeserialize)
    {
        StreamReader sr = new StreamReader(message.Stream);
        File.WriteAllText(@"C:\test.txt", sr.ReadToEnd());
        message.Stream.Position = 0; //Will blow up 'cause type of stream ("ConnectStream") doesn't alow seek so can't reset position
    }
}

Jika Anda mencari SoapMessage.Stream, ini seharusnya menjadi aliran hanya-baca yang dapat Anda gunakan untuk memeriksa data pada saat ini. Ini adalah penyebab yang kacau jika Anda membaca aliran, pemrosesan selanjutnya membom tanpa kesalahan data yang ditemukan (aliran berada di akhir) dan Anda tidak dapat mengatur ulang posisi ke awal.

Menariknya, jika Anda melakukan kedua metode, cara ChainStream dan ProcessMessage, metode ProcessMessage akan berfungsi karena Anda mengubah jenis aliran dari ConnectStream ke MemoryStream di ChainStream, dan MemoryStream mengizinkan operasi pencarian. (Saya mencoba mentransmisikan ConnectStream ke MemoryStream - tidak diizinkan.)

Jadi ..... Microsoft harus mengizinkan operasi pencarian pada tipe ChainStream atau membuat SoapMessage.Stream benar-benar salinan hanya-baca sebagaimana mestinya. (Tulis anggota kongres Anda, dll ...)

Satu hal lagi. Setelah membuat cara untuk mengambil respons HTTP mentah setelah pengecualian, saya masih belum mendapatkan respons penuh (seperti yang ditentukan oleh sniffer HTTP). Ini karena ketika layanan web pengembangan menambahkan pesan kesalahan HTML ke awal respons, itu tidak menyesuaikan header Panjang Konten, sehingga nilai Panjang Konten kurang dari ukuran isi respons yang sebenarnya. Yang saya dapatkan hanyalah jumlah karakter nilai Panjang Konten - sisanya hilang. Jelas, ketika .Net membaca aliran respons, itu hanya membaca jumlah karakter Panjang Konten dan tidak memungkinkan nilai Panjang Konten mungkin salah. Ini sebagaimana mestinya; tetapi jika nilai header Panjang Konten salah, satu-satunya cara Anda akan mendapatkan seluruh isi respons adalah dengan sniffer HTTP (Saya pengguna HTTP Analyzer darihttp://www.ieinspector.com ).

Chuck Bevitt
sumber
2

Saya lebih suka memiliki kerangka kerja yang melakukan pencatatan untuk Anda dengan menghubungkan aliran pencatatan yang mencatat sebagai kerangka proses yang mendasari aliran. Berikut ini tidak sebersih yang saya inginkan, karena Anda tidak dapat memutuskan antara permintaan dan tanggapan dalam metode ChainStream. Berikut cara saya menanganinya. Terima kasih kepada Jon Hanna untuk ide streaming yang utama

public class LoggerSoapExtension : SoapExtension
{
    private static readonly string LOG_DIRECTORY = ConfigurationManager.AppSettings["LOG_DIRECTORY"];
    private LogStream _logger;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }
    public override object GetInitializer(Type serviceType)
    {
        return null;
    }
    public override void Initialize(object initializer)
    {
    }
    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        _logger = new LogStream(stream);
        return _logger;
    }
    public override void ProcessMessage(SoapMessage message)
    {
        if (LOG_DIRECTORY != null)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    _logger.Type = "request";
                    break;
                case SoapMessageStage.AfterSerialize:
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    _logger.Type = "response";
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }
    }
    internal class LogStream : Stream
    {
        private Stream _source;
        private Stream _log;
        private bool _logSetup;
        private string _type;

        public LogStream(Stream source)
        {
            _source = source;
        }
        internal string Type
        {
            set { _type = value; }
        }
        private Stream Logger
        {
            get
            {
                if (!_logSetup)
                {
                    if (LOG_DIRECTORY != null)
                    {
                        try
                        {
                            DateTime now = DateTime.Now;
                            string folder = LOG_DIRECTORY + now.ToString("yyyyMMdd");
                            string subfolder = folder + "\\" + now.ToString("HH");
                            string client = System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Request != null && System.Web.HttpContext.Current.Request.UserHostAddress != null ? System.Web.HttpContext.Current.Request.UserHostAddress : string.Empty;
                            string ticks = now.ToString("yyyyMMdd'T'HHmmss.fffffff");
                            if (!Directory.Exists(folder))
                                Directory.CreateDirectory(folder);
                            if (!Directory.Exists(subfolder))
                                Directory.CreateDirectory(subfolder);
                            _log = new FileStream(new System.Text.StringBuilder(subfolder).Append('\\').Append(client).Append('_').Append(ticks).Append('_').Append(_type).Append(".xml").ToString(), FileMode.Create);
                        }
                        catch
                        {
                            _log = null;
                        }
                    }
                    _logSetup = true;
                }
                return _log;
            }
        }
        public override bool CanRead
        {
            get
            {
                return _source.CanRead;
            }
        }
        public override bool CanSeek
        {
            get
            {
                return _source.CanSeek;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return _source.CanWrite;
            }
        }

        public override long Length
        {
            get
            {
                return _source.Length;
            }
        }

        public override long Position
        {
            get
            {
                return _source.Position;
            }
            set
            {
                _source.Position = value;
            }
        }

        public override void Flush()
        {
            _source.Flush();
            if (Logger != null)
                Logger.Flush();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _source.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _source.SetLength(value);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            count = _source.Read(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
            return count;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _source.Write(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
        }
        public override int ReadByte()
        {
            int ret = _source.ReadByte();
            if (ret != -1 && Logger != null)
                Logger.WriteByte((byte)ret);
            return ret;
        }
        public override void Close()
        {
            _source.Close();
            if (Logger != null)
                Logger.Close();
            base.Close();
        }
        public override int ReadTimeout
        {
            get { return _source.ReadTimeout; }
            set { _source.ReadTimeout = value; }
        }
        public override int WriteTimeout
        {
            get { return _source.WriteTimeout; }
            set { _source.WriteTimeout = value; }
        }
    }
}
[AttributeUsage(AttributeTargets.Method)]
public class LoggerSoapExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1;
    public override int Priority
    {
        get
        {
            return priority;
        }
        set
        {
            priority = value;
        }
    }
    public override System.Type ExtensionType
    {
        get
        {
            return typeof(LoggerSoapExtension);
        }
    }
}

sumber
1
@TimCarter berhasil bagi saya, satu-satunya hal yang saya hapus adalah bagian klien, yang memberi saya nama dengan titik dua, menyebabkan pengecualian. Jadi begitu saya menghapus baris ini, ini berfungsi dengan baik bagi mereka yang ingin memiliki satu file untuk setiap permintaan / tanggapan. Satu-satunya hal yang perlu ditambahkan adalah sesuatu yang jelas jika Anda membaca jawaban lain, dan Anda harus menambahkan node web.config untuk menunjukkan soapExtension. Terima kasih!
netadictos
1

Berikut adalah versi sederhana dari jawaban teratas. Tambahkan ini ke <configuration>elemen file web.configatau Anda App.config. Ini akan membuat trace.logfile di bin/Debugfolder proyek Anda . Atau, Anda dapat menentukan jalur absolut untuk file log menggunakan initializeDataatribut.

  <system.diagnostics>
    <trace autoflush="true"/>
    <sources>
      <source name="System.Net" maxdatasize="9999" tracemode="protocolonly">
        <listeners>
          <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net" value="Verbose"/>
    </switches>
  </system.diagnostics>

Ini memperingatkan bahwa atribut maxdatasizedan tracemodetidak diperbolehkan, tetapi mereka meningkatkan jumlah data yang dapat dicatat, dan menghindari pencatatan semuanya dalam hex.

Collin Anderson
sumber
0

Anda belum menentukan bahasa apa yang Anda gunakan tetapi dengan asumsi C # / .NET Anda dapat menggunakan ekstensi SOAP .

Jika tidak, gunakan sniffer seperti Wireshark

rbrayb.dll
sumber
1
Ya, mencoba wireshark, itu hanya dapat menunjukkan kepada saya informasi header, konten sabun dienkripsi.
Andrew Harry
Itu mungkin karena Anda menggunakan https. Sejauh yang saya ingat, ekstensi SOAP berfungsi pada level yang lebih tinggi sehingga Anda dapat melihat data setelah didekripsi.
rbrayb
2
Gunakan Fiddler alih-alih Wireshark, itu mendekripsi https di luar kotak.
Rodrigo Strauss
-1

Saya menyadari saya cukup terlambat ke pesta, dan karena bahasa tidak benar-benar ditentukan, berikut adalah solusi VB.NET berdasarkan jawaban Bimmerbound, jika ada yang kebetulan menemukan ini dan membutuhkan solusi. Catatan: Anda perlu memiliki referensi ke kelas stringbuilder dalam proyek Anda, jika Anda belum memilikinya.

 Shared Function returnSerializedXML(ByVal obj As Object) As String
    Dim xmlSerializer As New System.Xml.Serialization.XmlSerializer(obj.GetType())
    Dim xmlSb As New StringBuilder
    Using textWriter As New IO.StringWriter(xmlSb)
        xmlSerializer.Serialize(textWriter, obj)
    End Using


    returnSerializedXML = xmlSb.ToString().Replace(vbCrLf, "")

End Function

Cukup panggil fungsinya dan itu akan mengembalikan string dengan serialisasi xml dari objek yang Anda coba kirimkan ke layanan web (secara realistis, ini harus bekerja untuk objek apa pun yang ingin Anda lemparkan juga).

Sebagai catatan tambahan, panggilan replace dalam fungsi sebelum mengembalikan xml adalah untuk menghapus karakter vbCrLf dari output. Milik saya memiliki banyak dari mereka dalam xml yang dihasilkan tetapi ini jelas akan bervariasi tergantung pada apa yang Anda coba untuk serialisasi, dan saya pikir mereka mungkin dilucuti selama objek dikirim ke layanan web.

pengguna2366842
sumber
Kepada siapa pun yang memberikan suara negatif kepada saya, jangan ragu untuk memberi tahu saya apa yang saya lewatkan / apa yang dapat ditingkatkan.
pengguna2366842